*/
var dispatches = [
/**
Dispatched when runtime is connected and file-picker is ready to be used.
@event ready
@param {Object} event
*/
'ready',
/**
Dispatched right after [ready](#event_ready) event, and whenever [refresh()](#method_refresh) is invoked.
Check [corresponding documentation entry](#method_refresh) for more info.
@event refresh
@param {Object} event
*/
/**
Dispatched when selection of files in the dialog is complete.
@event change
@param {Object} event
*/
'change',
'cancel', // TODO: might be useful
/**
Dispatched when mouse cursor enters file-picker area. Can be used to style element
accordingly.
@event mouseenter
@param {Object} event
*/
'mouseenter',
/**
Dispatched when mouse cursor leaves file-picker area. Can be used to style element
accordingly.
@event mouseleave
@param {Object} event
*/
'mouseleave',
/**
Dispatched when functional mouse button is pressed on top of file-picker area.
@event mousedown
@param {Object} event
*/
'mousedown',
/**
Dispatched when functional mouse button is released on top of file-picker area.
@event mouseup
@param {Object} event
*/
'mouseup'
];
function FileInput(options) {
var self = this,
container, browseButton, defaults;
// if flat argument passed it should be browse_button id
if (Basic.inArray(Basic.typeOf(options), ['string', 'node']) !== -1) {
options = { browse_button : options };
}
// this will help us to find proper default container
browseButton = Dom.get(options.browse_button);
if (!browseButton) {
// browse button is required
throw new x.DOMException(x.DOMException.NOT_FOUND_ERR);
}
// figure out the options
defaults = {
accept: [{
title: I18n.translate('All Files'),
extensions: '*'
}],
name: 'file',
multiple: false,
required_caps: false,
container: browseButton.parentNode || document.body
};
options = Basic.extend({}, defaults, options);
// convert to object representation
if (typeof(options.required_caps) === 'string') {
options.required_caps = Runtime.parseCaps(options.required_caps);
}
// normalize accept option (could be list of mime types or array of title/extensions pairs)
if (typeof(options.accept) === 'string') {
options.accept = Mime.mimes2extList(options.accept);
}
container = Dom.get(options.container);
// make sure we have container
if (!container) {
container = document.body;
}
// make container relative, if it's not
if (Dom.getStyle(container, 'position') === 'static') {
container.style.position = 'relative';
}
container = browseButton = null; // IE
RuntimeClient.call(self);
Basic.extend(self, {
/**
Unique id of the component
@property uid
@protected
@readOnly
@type {String}
@default UID
*/
uid: Basic.guid('uid_'),
/**
Unique id of the connected runtime, if any.
@property ruid
@protected
@type {String}
*/
ruid: null,
/**
Unique id of the runtime container. Useful to get hold of it for various manipulations.
@property shimid
@protected
@type {String}
*/
shimid: null,
/**
Array of selected mOxie.File objects
@property files
@type {Array}
@default null
*/
files: null,
/**
Initializes the file-picker, connects it to runtime and dispatches event ready when done.
@method init
*/
init: function() {
self.convertEventPropsToHandlers(dispatches);
self.bind('RuntimeInit', function(e, runtime) {
self.ruid = runtime.uid;
self.shimid = runtime.shimid;
self.bind("Ready", function() {
self.trigger("Refresh");
}, 999);
self.bind("Change", function() {
var files = runtime.exec.call(self, 'FileInput', 'getFiles');
self.files = [];
Basic.each(files, function(file) {
// ignore empty files (IE10 for example hangs if you try to send them via XHR)
if (file.size === 0) {
return true;
}
self.files.push(new File(self.ruid, file));
});
}, 999);
// re-position and resize shim container
self.bind('Refresh', function() {
var pos, size, browseButton, shimContainer;
browseButton = Dom.get(options.browse_button);
shimContainer = Dom.get(runtime.shimid); // do not use runtime.getShimContainer(), since it will create container if it doesn't exist
if (browseButton) {
pos = Dom.getPos(browseButton, Dom.get(options.container));
size = Dom.getSize(browseButton);
if (shimContainer) {
Basic.extend(shimContainer.style, {
top : pos.y + 'px',
left : pos.x + 'px',
width : size.w + 'px',
height : size.h + 'px'
});
}
}
shimContainer = browseButton = null;
});
runtime.exec.call(self, 'FileInput', 'init', options);
});
// runtime needs: options.required_features, options.runtime_order and options.container
self.connectRuntime(Basic.extend({}, options, {
required_caps: {
select_file: true
}
}));
},
/**
Disables file-picker element, so that it doesn't react to mouse clicks.
@method disable
@param {Boolean} [state=true] Disable component if - true, enable if - false
*/
disable: function(state) {
var runtime = this.getRuntime();
if (runtime) {
runtime.exec.call(this, 'FileInput', 'disable', Basic.typeOf(state) === 'undefined' ? true : state);
}
},
/**
Reposition and resize dialog trigger to match the position and size of browse_button element.
@method refresh
*/
refresh: function() {
self.trigger("Refresh");
},
/**
Destroy component.
@method destroy
*/
destroy: function() {
var runtime = this.getRuntime();
if (runtime) {
runtime.exec.call(this, 'FileInput', 'destroy');
this.disconnectRuntime();
}
if (Basic.typeOf(this.files) === 'array') {
// no sense in leaving associated files behind
Basic.each(this.files, function(file) {
file.destroy();
});
}
this.files = null;
}
});
}
FileInput.prototype = EventTarget.instance;
return FileInput;
});
// Included from: src/javascript/file/FileDrop.js
/**
* FileDrop.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
define('moxie/file/FileDrop', [
'moxie/core/I18n',
'moxie/core/utils/Dom',
'moxie/core/Exceptions',
'moxie/core/utils/Basic',
'moxie/file/File',
'moxie/runtime/RuntimeClient',
'moxie/core/EventTarget',
'moxie/core/utils/Mime'
], function(I18n, Dom, x, Basic, File, RuntimeClient, EventTarget, Mime) {
/**
Turn arbitrary DOM element to a drop zone accepting files. Converts selected files to _File_ objects, to be used
in conjunction with _Image_, preloaded in memory with _FileReader_ or uploaded to a server through
_XMLHttpRequest_.
@example
Drop files here
@class FileDrop
@constructor
@extends EventTarget
@uses RuntimeClient
@param {Object|String} options If options has typeof string, argument is considered as options.drop_zone
@param {String|DOMElement} options.drop_zone DOM Element to turn into a drop zone
@param {Array} [options.accept] Array of mime types to accept. By default accepts all
@param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support
*/
var dispatches = [
/**
Dispatched when runtime is connected and drop zone is ready to accept files.
@event ready
@param {Object} event
*/
'ready',
/**
Dispatched when dragging cursor enters the drop zone.
@event dragenter
@param {Object} event
*/
'dragenter',
/**
Dispatched when dragging cursor leaves the drop zone.
@event dragleave
@param {Object} event
*/
'dragleave',
/**
Dispatched when file is dropped onto the drop zone.
@event drop
@param {Object} event
*/
'drop',
/**
Dispatched if error occurs.
@event error
@param {Object} event
*/
'error'
];
function FileDrop(options) {
var self = this, defaults;
// if flat argument passed it should be drop_zone id
if (typeof(options) === 'string') {
options = { drop_zone : options };
}
// figure out the options
defaults = {
accept: [{
title: I18n.translate('All Files'),
extensions: '*'
}],
required_caps: {
drag_and_drop: true
}
};
options = typeof(options) === 'object' ? Basic.extend({}, defaults, options) : defaults;
// this will help us to find proper default container
options.container = Dom.get(options.drop_zone) || document.body;
// make container relative, if it is not
if (Dom.getStyle(options.container, 'position') === 'static') {
options.container.style.position = 'relative';
}
// normalize accept option (could be list of mime types or array of title/extensions pairs)
if (typeof(options.accept) === 'string') {
options.accept = Mime.mimes2extList(options.accept);
}
RuntimeClient.call(self);
Basic.extend(self, {
uid: Basic.guid('uid_'),
ruid: null,
files: null,
init: function() {
self.convertEventPropsToHandlers(dispatches);
self.bind('RuntimeInit', function(e, runtime) {
self.ruid = runtime.uid;
self.bind("Drop", function() {
var files = runtime.exec.call(self, 'FileDrop', 'getFiles');
self.files = [];
Basic.each(files, function(file) {
self.files.push(new File(self.ruid, file));
});
}, 999);
runtime.exec.call(self, 'FileDrop', 'init', options);
self.dispatchEvent('ready');
});
// runtime needs: options.required_features, options.runtime_order and options.container
self.connectRuntime(options); // throws RuntimeError
},
destroy: function() {
var runtime = this.getRuntime();
if (runtime) {
runtime.exec.call(this, 'FileDrop', 'destroy');
this.disconnectRuntime();
}
this.files = null;
}
});
}
FileDrop.prototype = EventTarget.instance;
return FileDrop;
});
// Included from: src/javascript/runtime/RuntimeTarget.js
/**
* RuntimeTarget.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
define('moxie/runtime/RuntimeTarget', [
'moxie/core/utils/Basic',
'moxie/runtime/RuntimeClient',
"moxie/core/EventTarget"
], function(Basic, RuntimeClient, EventTarget) {
/**
Instance of this class can be used as a target for the events dispatched by shims,
when allowing them onto components is for either reason inappropriate
@class RuntimeTarget
@constructor
@protected
@extends EventTarget
*/
function RuntimeTarget() {
this.uid = Basic.guid('uid_');
RuntimeClient.call(this);
this.destroy = function() {
this.disconnectRuntime();
this.unbindAll();
};
}
RuntimeTarget.prototype = EventTarget.instance;
return RuntimeTarget;
});
// Included from: src/javascript/file/FileReader.js
/**
* FileReader.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
define('moxie/file/FileReader', [
'moxie/core/utils/Basic',
'moxie/core/utils/Encode',
'moxie/core/Exceptions',
'moxie/core/EventTarget',
'moxie/file/Blob',
'moxie/file/File',
'moxie/runtime/RuntimeTarget'
], function(Basic, Encode, x, EventTarget, Blob, File, RuntimeTarget) {
/**
Utility for preloading o.Blob/o.File objects in memory. By design closely follows [W3C FileReader](http://www.w3.org/TR/FileAPI/#dfn-filereader)
interface. Where possible uses native FileReader, where - not falls back to shims.
@class FileReader
@constructor FileReader
@extends EventTarget
@uses RuntimeClient
*/
var dispatches = [
/**
Dispatched when the read starts.
@event loadstart
@param {Object} event
*/
'loadstart',
/**
Dispatched while reading (and decoding) blob, and reporting partial Blob data (progess.loaded/progress.total).
@event progress
@param {Object} event
*/
'progress',
/**
Dispatched when the read has successfully completed.
@event load
@param {Object} event
*/
'load',
/**
Dispatched when the read has been aborted. For instance, by invoking the abort() method.
@event abort
@param {Object} event
*/
'abort',
/**
Dispatched when the read has failed.
@event error
@param {Object} event
*/
'error',
/**
Dispatched when the request has completed (either in success or failure).
@event loadend
@param {Object} event
*/
'loadend'
];
function FileReader() {
var self = this, _fr;
Basic.extend(this, {
/**
UID of the component instance.
@property uid
@type {String}
*/
uid: Basic.guid('uid_'),
/**
Contains current state of FileReader object. Can take values of FileReader.EMPTY, FileReader.LOADING
and FileReader.DONE.
@property readyState
@type {Number}
@default FileReader.EMPTY
*/
readyState: FileReader.EMPTY,
/**
Result of the successful read operation.
@property result
@type {String}
*/
result: null,
/**
Stores the error of failed asynchronous read operation.
@property error
@type {DOMError}
*/
error: null,
/**
Initiates reading of File/Blob object contents to binary string.
@method readAsBinaryString
@param {Blob|File} blob Object to preload
*/
readAsBinaryString: function(blob) {
_read.call(this, 'readAsBinaryString', blob);
},
/**
Initiates reading of File/Blob object contents to dataURL string.
@method readAsDataURL
@param {Blob|File} blob Object to preload
*/
readAsDataURL: function(blob) {
_read.call(this, 'readAsDataURL', blob);
},
/**
Initiates reading of File/Blob object contents to string.
@method readAsText
@param {Blob|File} blob Object to preload
*/
readAsText: function(blob) {
_read.call(this, 'readAsText', blob);
},
/**
Aborts preloading process.
@method abort
*/
abort: function() {
this.result = null;
if (Basic.inArray(this.readyState, [FileReader.EMPTY, FileReader.DONE]) !== -1) {
return;
} else if (this.readyState === FileReader.LOADING) {
this.readyState = FileReader.DONE;
}
if (_fr) {
_fr.getRuntime().exec.call(this, 'FileReader', 'abort');
}
this.trigger('abort');
this.trigger('loadend');
},
/**
Destroy component and release resources.
@method destroy
*/
destroy: function() {
this.abort();
if (_fr) {
_fr.getRuntime().exec.call(this, 'FileReader', 'destroy');
_fr.disconnectRuntime();
}
self = _fr = null;
}
});
function _read(op, blob) {
_fr = new RuntimeTarget();
function error(err) {
self.readyState = FileReader.DONE;
self.error = err;
self.trigger('error');
loadEnd();
}
function loadEnd() {
_fr.destroy();
_fr = null;
self.trigger('loadend');
}
function exec(runtime) {
_fr.bind('Error', function(e, err) {
error(err);
});
_fr.bind('Progress', function(e) {
self.result = runtime.exec.call(_fr, 'FileReader', 'getResult');
self.trigger(e);
});
_fr.bind('Load', function(e) {
self.readyState = FileReader.DONE;
self.result = runtime.exec.call(_fr, 'FileReader', 'getResult');
self.trigger(e);
loadEnd();
});
runtime.exec.call(_fr, 'FileReader', 'read', op, blob);
}
this.convertEventPropsToHandlers(dispatches);
if (this.readyState === FileReader.LOADING) {
return error(new x.DOMException(x.DOMException.INVALID_STATE_ERR));
}
this.readyState = FileReader.LOADING;
this.trigger('loadstart');
// if source is o.Blob/o.File
if (blob instanceof Blob) {
if (blob.isDetached()) {
var src = blob.getSource();
switch (op) {
case 'readAsText':
case 'readAsBinaryString':
this.result = src;
break;
case 'readAsDataURL':
this.result = 'data:' + blob.type + ';base64,' + Encode.btoa(src);
break;
}
this.readyState = FileReader.DONE;
this.trigger('load');
loadEnd();
} else {
exec(_fr.connectRuntime(blob.ruid));
}
} else {
error(new x.DOMException(x.DOMException.NOT_FOUND_ERR));
}
}
}
/**
Initial FileReader state
@property EMPTY
@type {Number}
@final
@static
@default 0
*/
FileReader.EMPTY = 0;
/**
FileReader switches to this state when it is preloading the source
@property LOADING
@type {Number}
@final
@static
@default 1
*/
FileReader.LOADING = 1;
/**
Preloading is complete, this is a final state
@property DONE
@type {Number}
@final
@static
@default 2
*/
FileReader.DONE = 2;
FileReader.prototype = EventTarget.instance;
return FileReader;
});
// Included from: src/javascript/core/utils/Url.js
/**
* Url.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
define('moxie/core/utils/Url', [], function() {
/**
Parse url into separate components and fill in absent parts with parts from current url,
based on https://raw.github.com/kvz/phpjs/master/functions/url/parse_url.js
@method parseUrl
@for Utils
@static
@param {String} url Url to parse (defaults to empty string if undefined)
@return {Object} Hash containing extracted uri components
*/
var parseUrl = function(url, currentUrl) {
var key = ['source', 'scheme', 'authority', 'userInfo', 'user', 'pass', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment']
, i = key.length
, ports = {
http: 80,
https: 443
}
, uri = {}
, regex = /^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/
, m = regex.exec(url || '')
;
while (i--) {
if (m[i]) {
uri[key[i]] = m[i];
}
}
// when url is relative, we set the origin and the path ourselves
if (!uri.scheme) {
// come up with defaults
if (!currentUrl || typeof(currentUrl) === 'string') {
currentUrl = parseUrl(currentUrl || document.location.href);
}
uri.scheme = currentUrl.scheme;
uri.host = currentUrl.host;
uri.port = currentUrl.port;
var path = '';
// for urls without trailing slash we need to figure out the path
if (/^[^\/]/.test(uri.path)) {
path = currentUrl.path;
// if path ends with a filename, strip it
if (!/(\/|\/[^\.]+)$/.test(path)) {
path = path.replace(/\/[^\/]+$/, '/');
} else {
path += '/';
}
}
uri.path = path + (uri.path || ''); // site may reside at domain.com or domain.com/subdir
}
if (!uri.port) {
uri.port = ports[uri.scheme] || 80;
}
uri.port = parseInt(uri.port, 10);
if (!uri.path) {
uri.path = "/";
}
delete uri.source;
return uri;
};
/**
Resolve url - among other things will turn relative url to absolute
@method resolveUrl
@static
@param {String} url Either absolute or relative
@return {String} Resolved, absolute url
*/
var resolveUrl = function(url) {
var ports = { // we ignore default ports
http: 80,
https: 443
}
, urlp = parseUrl(url)
;
return urlp.scheme + '://' + urlp.host + (urlp.port !== ports[urlp.scheme] ? ':' + urlp.port : '') + urlp.path + (urlp.query ? urlp.query : '');
};
/**
Check if specified url has the same origin as the current document
@method hasSameOrigin
@param {String|Object} url
@return {Boolean}
*/
var hasSameOrigin = function(url) {
function origin(url) {
return [url.scheme, url.host, url.port].join('/');
}
if (typeof url === 'string') {
url = parseUrl(url);
}
return origin(parseUrl()) === origin(url);
};
return {
parseUrl: parseUrl,
resolveUrl: resolveUrl,
hasSameOrigin: hasSameOrigin
};
});
// Included from: src/javascript/file/FileReaderSync.js
/**
* FileReaderSync.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
define('moxie/file/FileReaderSync', [
'moxie/core/utils/Basic',
'moxie/runtime/RuntimeClient',
'moxie/core/utils/Encode'
], function(Basic, RuntimeClient, Encode) {
/**
Synchronous FileReader implementation. Something like this is available in WebWorkers environment, here
it can be used to read only preloaded blobs/files and only below certain size (not yet sure what that'd be,
but probably < 1mb). Not meant to be used directly by user.
@class FileReaderSync
@private
@constructor
*/
return function() {
RuntimeClient.call(this);
Basic.extend(this, {
uid: Basic.guid('uid_'),
readAsBinaryString: function(blob) {
return _read.call(this, 'readAsBinaryString', blob);
},
readAsDataURL: function(blob) {
return _read.call(this, 'readAsDataURL', blob);
},
/*readAsArrayBuffer: function(blob) {
return _read.call(this, 'readAsArrayBuffer', blob);
},*/
readAsText: function(blob) {
return _read.call(this, 'readAsText', blob);
}
});
function _read(op, blob) {
if (blob.isDetached()) {
var src = blob.getSource();
switch (op) {
case 'readAsBinaryString':
return src;
case 'readAsDataURL':
return 'data:' + blob.type + ';base64,' + Encode.btoa(src);
case 'readAsText':
var txt = '';
for (var i = 0, length = src.length; i < length; i++) {
txt += String.fromCharCode(src[i]);
}
return txt;
}
} else {
var result = this.connectRuntime(blob.ruid).exec.call(this, 'FileReaderSync', 'read', op, blob);
this.disconnectRuntime();
return result;
}
}
};
});
// Included from: src/javascript/xhr/FormData.js
/**
* FormData.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
define("moxie/xhr/FormData", [
"moxie/core/Exceptions",
"moxie/core/utils/Basic",
"moxie/file/Blob"
], function(x, Basic, Blob) {
/**
FormData
@class FormData
@constructor
*/
function FormData() {
var _blob, _fields = [];
Basic.extend(this, {
/**
Append another key-value pair to the FormData object
@method append
@param {String} name Name for the new field
@param {String|Blob|Array|Object} value Value for the field
*/
append: function(name, value) {
var self = this, valueType = Basic.typeOf(value);
// according to specs value might be either Blob or String
if (value instanceof Blob) {
_blob = {
name: name,
value: value // unfortunately we can only send single Blob in one FormData
};
} else if ('array' === valueType) {
name += '[]';
Basic.each(value, function(value) {
self.append(name, value);
});
} else if ('object' === valueType) {
Basic.each(value, function(value, key) {
self.append(name + '[' + key + ']', value);
});
} else if ('null' === valueType || 'undefined' === valueType || 'number' === valueType && isNaN(value)) {
self.append(name, "false");
} else {
_fields.push({
name: name,
value: value.toString()
});
}
},
/**
Checks if FormData contains Blob.
@method hasBlob
@return {Boolean}
*/
hasBlob: function() {
return !!this.getBlob();
},
/**
Retrieves blob.
@method getBlob
@return {Object} Either Blob if found or null
*/
getBlob: function() {
return _blob && _blob.value || null;
},
/**
Retrieves blob field name.
@method getBlobName
@return {String} Either Blob field name or null
*/
getBlobName: function() {
return _blob && _blob.name || null;
},
/**
Loop over the fields in FormData and invoke the callback for each of them.
@method each
@param {Function} cb Callback to call for each field
*/
each: function(cb) {
Basic.each(_fields, function(field) {
cb(field.value, field.name);
});
if (_blob) {
cb(_blob.value, _blob.name);
}
},
destroy: function() {
_blob = null;
_fields = [];
}
});
}
return FormData;
});
// Included from: src/javascript/xhr/XMLHttpRequest.js
/**
* XMLHttpRequest.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
define("moxie/xhr/XMLHttpRequest", [
"moxie/core/utils/Basic",
"moxie/core/Exceptions",
"moxie/core/EventTarget",
"moxie/core/utils/Encode",
"moxie/core/utils/Url",
"moxie/runtime/Runtime",
"moxie/runtime/RuntimeTarget",
"moxie/file/Blob",
"moxie/file/FileReaderSync",
"moxie/xhr/FormData",
"moxie/core/utils/Env",
"moxie/core/utils/Mime"
], function(Basic, x, EventTarget, Encode, Url, Runtime, RuntimeTarget, Blob, FileReaderSync, FormData, Env, Mime) {
var httpCode = {
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing',
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
207: 'Multi-Status',
226: 'IM Used',
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Found',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
306: 'Reserved',
307: 'Temporary Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Timeout',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Request Entity Too Large',
414: 'Request-URI Too Long',
415: 'Unsupported Media Type',
416: 'Requested Range Not Satisfiable',
417: 'Expectation Failed',
422: 'Unprocessable Entity',
423: 'Locked',
424: 'Failed Dependency',
426: 'Upgrade Required',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported',
506: 'Variant Also Negotiates',
507: 'Insufficient Storage',
510: 'Not Extended'
};
function XMLHttpRequestUpload() {
this.uid = Basic.guid('uid_');
}
XMLHttpRequestUpload.prototype = EventTarget.instance;
/**
Implementation of XMLHttpRequest
@class XMLHttpRequest
@constructor
@uses RuntimeClient
@extends EventTarget
*/
var dispatches = ['loadstart', 'progress', 'abort', 'error', 'load', 'timeout', 'loadend']; // & readystatechange (for historical reasons)
var NATIVE = 1, RUNTIME = 2;
function XMLHttpRequest() {
var self = this,
// this (together with _p() @see below) is here to gracefully upgrade to setter/getter syntax where possible
props = {
/**
The amount of milliseconds a request can take before being terminated. Initially zero. Zero means there is no timeout.
@property timeout
@type Number
@default 0
*/
timeout: 0,
/**
Current state, can take following values:
UNSENT (numeric value 0)
The object has been constructed.
OPENED (numeric value 1)
The open() method has been successfully invoked. During this state request headers can be set using setRequestHeader() and the request can be made using the send() method.
HEADERS_RECEIVED (numeric value 2)
All redirects (if any) have been followed and all HTTP headers of the final response have been received. Several response members of the object are now available.
LOADING (numeric value 3)
The response entity body is being received.
DONE (numeric value 4)
@property readyState
@type Number
@default 0 (UNSENT)
*/
readyState: XMLHttpRequest.UNSENT,
/**
True when user credentials are to be included in a cross-origin request. False when they are to be excluded
in a cross-origin request and when cookies are to be ignored in its response. Initially false.
@property withCredentials
@type Boolean
@default false
*/
withCredentials: false,
/**
Returns the HTTP status code.
@property status
@type Number
@default 0
*/
status: 0,
/**
Returns the HTTP status text.
@property statusText
@type String
*/
statusText: "",
/**
Returns the response type. Can be set to change the response type. Values are:
the empty string (default), "arraybuffer", "blob", "document", "json", and "text".
@property responseType
@type String
*/
responseType: "",
/**
Returns the document response entity body.
Throws an "InvalidStateError" exception if responseType is not the empty string or "document".
@property responseXML
@type Document
*/
responseXML: null,
/**
Returns the text response entity body.
Throws an "InvalidStateError" exception if responseType is not the empty string or "text".
@property responseText
@type String
*/
responseText: null,
/**
Returns the response entity body (http://www.w3.org/TR/XMLHttpRequest/#response-entity-body).
Can become: ArrayBuffer, Blob, Document, JSON, Text
@property response
@type Mixed
*/
response: null
},
_async = true,
_url,
_method,
_headers = {},
_user,
_password,
_encoding = null,
_mimeType = null,
// flags
_sync_flag = false,
_send_flag = false,
_upload_events_flag = false,
_upload_complete_flag = false,
_error_flag = false,
_same_origin_flag = false,
// times
_start_time,
_timeoutset_time,
_finalMime = null,
_finalCharset = null,
_options = {},
_xhr,
_responseHeaders = '',
_responseHeadersBag
;
Basic.extend(this, props, {
/**
Unique id of the component
@property uid
@type String
*/
uid: Basic.guid('uid_'),
/**
Target for Upload events
@property upload
@type XMLHttpRequestUpload
*/
upload: new XMLHttpRequestUpload(),
/**
Sets the request method, request URL, synchronous flag, request username, and request password.
Throws a "SyntaxError" exception if one of the following is true:
method is not a valid HTTP method.
url cannot be resolved.
url contains the "user:password" format in the userinfo production.
Throws a "SecurityError" exception if method is a case-insensitive match for CONNECT, TRACE or TRACK.
Throws an "InvalidAccessError" exception if one of the following is true:
Either user or password is passed as argument and the origin of url does not match the XMLHttpRequest origin.
There is an associated XMLHttpRequest document and either the timeout attribute is not zero,
the withCredentials attribute is true, or the responseType attribute is not the empty string.
@method open
@param {String} method HTTP method to use on request
@param {String} url URL to request
@param {Boolean} [async=true] If false request will be done in synchronous manner. Asynchronous by default.
@param {String} [user] Username to use in HTTP authentication process on server-side
@param {String} [password] Password to use in HTTP authentication process on server-side
*/
open: function(method, url, async, user, password) {
var urlp;
// first two arguments are required
if (!method || !url) {
throw new x.DOMException(x.DOMException.SYNTAX_ERR);
}
// 2 - check if any code point in method is higher than U+00FF or after deflating method it does not match the method
if (/[\u0100-\uffff]/.test(method) || Encode.utf8_encode(method) !== method) {
throw new x.DOMException(x.DOMException.SYNTAX_ERR);
}
// 3
if (!!~Basic.inArray(method.toUpperCase(), ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'TRACE', 'TRACK'])) {
_method = method.toUpperCase();
}
// 4 - allowing these methods poses a security risk
if (!!~Basic.inArray(_method, ['CONNECT', 'TRACE', 'TRACK'])) {
throw new x.DOMException(x.DOMException.SECURITY_ERR);
}
// 5
url = Encode.utf8_encode(url);
// 6 - Resolve url relative to the XMLHttpRequest base URL. If the algorithm returns an error, throw a "SyntaxError".
urlp = Url.parseUrl(url);
_same_origin_flag = Url.hasSameOrigin(urlp);
// 7 - manually build up absolute url
_url = Url.resolveUrl(url);
// 9-10, 12-13
if ((user || password) && !_same_origin_flag) {
throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
}
_user = user || urlp.user;
_password = password || urlp.pass;
// 11
_async = async || true;
if (_async === false && (_p('timeout') || _p('withCredentials') || _p('responseType') !== "")) {
throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
}
// 14 - terminate abort()
// 15 - terminate send()
// 18
_sync_flag = !_async;
_send_flag = false;
_headers = {};
_reset.call(this);
// 19
_p('readyState', XMLHttpRequest.OPENED);
// 20
this.convertEventPropsToHandlers(['readystatechange']); // unify event handlers
this.dispatchEvent('readystatechange');
},
/**
Appends an header to the list of author request headers, or if header is already
in the list of author request headers, combines its value with value.
Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set.
Throws a "SyntaxError" exception if header is not a valid HTTP header field name or if value
is not a valid HTTP header field value.
@method setRequestHeader
@param {String} header
@param {String|Number} value
*/
setRequestHeader: function(header, value) {
var uaHeaders = [ // these headers are controlled by the user agent
"accept-charset",
"accept-encoding",
"access-control-request-headers",
"access-control-request-method",
"connection",
"content-length",
"cookie",
"cookie2",
"content-transfer-encoding",
"date",
"expect",
"host",
"keep-alive",
"origin",
"referer",
"te",
"trailer",
"transfer-encoding",
"upgrade",
"user-agent",
"via"
];
// 1-2
if (_p('readyState') !== XMLHttpRequest.OPENED || _send_flag) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
// 3
if (/[\u0100-\uffff]/.test(header) || Encode.utf8_encode(header) !== header) {
throw new x.DOMException(x.DOMException.SYNTAX_ERR);
}
// 4
/* this step is seemingly bypassed in browsers, probably to allow various unicode characters in header values
if (/[\u0100-\uffff]/.test(value) || Encode.utf8_encode(value) !== value) {
throw new x.DOMException(x.DOMException.SYNTAX_ERR);
}*/
header = Basic.trim(header).toLowerCase();
// setting of proxy-* and sec-* headers is prohibited by spec
if (!!~Basic.inArray(header, uaHeaders) || /^(proxy\-|sec\-)/.test(header)) {
return false;
}
// camelize
// browsers lowercase header names (at least for custom ones)
// header = header.replace(/\b\w/g, function($1) { return $1.toUpperCase(); });
if (!_headers[header]) {
_headers[header] = value;
} else {
// http://tools.ietf.org/html/rfc2616#section-4.2 (last paragraph)
_headers[header] += ', ' + value;
}
return true;
},
/**
Returns all headers from the response, with the exception of those whose field name is Set-Cookie or Set-Cookie2.
@method getAllResponseHeaders
@return {String} reponse headers or empty string
*/
getAllResponseHeaders: function() {
return _responseHeaders || '';
},
/**
Returns the header field value from the response of which the field name matches header,
unless the field name is Set-Cookie or Set-Cookie2.
@method getResponseHeader
@param {String} header
@return {String} value(s) for the specified header or null
*/
getResponseHeader: function(header) {
header = header.toLowerCase();
if (_error_flag || !!~Basic.inArray(header, ['set-cookie', 'set-cookie2'])) {
return null;
}
if (_responseHeaders && _responseHeaders !== '') {
// if we didn't parse response headers until now, do it and keep for later
if (!_responseHeadersBag) {
_responseHeadersBag = {};
Basic.each(_responseHeaders.split(/\r\n/), function(line) {
var pair = line.split(/:\s+/);
if (pair.length === 2) { // last line might be empty, omit
pair[0] = Basic.trim(pair[0]); // just in case
_responseHeadersBag[pair[0].toLowerCase()] = { // simply to retain header name in original form
header: pair[0],
value: Basic.trim(pair[1])
};
}
});
}
if (_responseHeadersBag.hasOwnProperty(header)) {
return _responseHeadersBag[header].header + ': ' + _responseHeadersBag[header].value;
}
}
return null;
},
/**
Sets the Content-Type header for the response to mime.
Throws an "InvalidStateError" exception if the state is LOADING or DONE.
Throws a "SyntaxError" exception if mime is not a valid media type.
@method overrideMimeType
@param String mime Mime type to set
*/
overrideMimeType: function(mime) {
var matches, charset;
// 1
if (!!~Basic.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
// 2
mime = Basic.trim(mime.toLowerCase());
if (/;/.test(mime) && (matches = mime.match(/^([^;]+)(?:;\scharset\=)?(.*)$/))) {
mime = matches[1];
if (matches[2]) {
charset = matches[2];
}
}
if (!Mime.mimes[mime]) {
throw new x.DOMException(x.DOMException.SYNTAX_ERR);
}
// 3-4
_finalMime = mime;
_finalCharset = charset;
},
/**
Initiates the request. The optional argument provides the request entity body.
The argument is ignored if request method is GET or HEAD.
Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set.
@method send
@param {Blob|Document|String|FormData} [data] Request entity body
@param {Object} [options] Set of requirements and pre-requisities for runtime initialization
*/
send: function(data, options) {
if (Basic.typeOf(options) === 'string') {
_options = { ruid: options };
} else if (!options) {
_options = {};
} else {
_options = options;
}
this.convertEventPropsToHandlers(dispatches);
this.upload.convertEventPropsToHandlers(dispatches);
// 1-2
if (this.readyState !== XMLHttpRequest.OPENED || _send_flag) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
// 3
// sending Blob
if (data instanceof Blob) {
_options.ruid = data.ruid;
_mimeType = data.type || 'application/octet-stream';
}
// FormData
else if (data instanceof FormData) {
if (data.hasBlob()) {
var blob = data.getBlob();
_options.ruid = blob.ruid;
_mimeType = blob.type || 'application/octet-stream';
}
}
// DOMString
else if (typeof data === 'string') {
_encoding = 'UTF-8';
_mimeType = 'text/plain;charset=UTF-8';
// data should be converted to Unicode and encoded as UTF-8
data = Encode.utf8_encode(data);
}
// if withCredentials not set, but requested, set it automatically
if (!this.withCredentials) {
this.withCredentials = (_options.required_caps && _options.required_caps.send_browser_cookies) && !_same_origin_flag;
}
// 4 - storage mutex
// 5
_upload_events_flag = (!_sync_flag && this.upload.hasEventListener()); // DSAP
// 6
_error_flag = false;
// 7
_upload_complete_flag = !data;
// 8 - Asynchronous steps
if (!_sync_flag) {
// 8.1
_send_flag = true;
// 8.2
// this.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr
// 8.3
//if (!_upload_complete_flag) {
// this.upload.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr
//}
}
// 8.5 - Return the send() method call, but continue running the steps in this algorithm.
_doXHR.call(this, data);
},
/**
Cancels any network activity.
@method abort
*/
abort: function() {
_error_flag = true;
_sync_flag = false;
if (!~Basic.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED, XMLHttpRequest.DONE])) {
_p('readyState', XMLHttpRequest.DONE);
_send_flag = false;
if (_xhr) {
_xhr.getRuntime().exec.call(_xhr, 'XMLHttpRequest', 'abort', _upload_complete_flag);
} else {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
_upload_complete_flag = true;
} else {
_p('readyState', XMLHttpRequest.UNSENT);
}
},
destroy: function() {
if (_xhr) {
if (Basic.typeOf(_xhr.destroy) === 'function') {
_xhr.destroy();
}
_xhr = null;
}
this.unbindAll();
if (this.upload) {
this.upload.unbindAll();
this.upload = null;
}
}
});
/* this is nice, but maybe too lengthy
// if supported by JS version, set getters/setters for specific properties
o.defineProperty(this, 'readyState', {
configurable: false,
get: function() {
return _p('readyState');
}
});
o.defineProperty(this, 'timeout', {
configurable: false,
get: function() {
return _p('timeout');
},
set: function(value) {
if (_sync_flag) {
throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
}
// timeout still should be measured relative to the start time of request
_timeoutset_time = (new Date).getTime();
_p('timeout', value);
}
});
// the withCredentials attribute has no effect when fetching same-origin resources
o.defineProperty(this, 'withCredentials', {
configurable: false,
get: function() {
return _p('withCredentials');
},
set: function(value) {
// 1-2
if (!~o.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED]) || _send_flag) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
// 3-4
if (_anonymous_flag || _sync_flag) {
throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
}
// 5
_p('withCredentials', value);
}
});
o.defineProperty(this, 'status', {
configurable: false,
get: function() {
return _p('status');
}
});
o.defineProperty(this, 'statusText', {
configurable: false,
get: function() {
return _p('statusText');
}
});
o.defineProperty(this, 'responseType', {
configurable: false,
get: function() {
return _p('responseType');
},
set: function(value) {
// 1
if (!!~o.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
// 2
if (_sync_flag) {
throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR);
}
// 3
_p('responseType', value.toLowerCase());
}
});
o.defineProperty(this, 'responseText', {
configurable: false,
get: function() {
// 1
if (!~o.inArray(_p('responseType'), ['', 'text'])) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
// 2-3
if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
return _p('responseText');
}
});
o.defineProperty(this, 'responseXML', {
configurable: false,
get: function() {
// 1
if (!~o.inArray(_p('responseType'), ['', 'document'])) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
// 2-3
if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
return _p('responseXML');
}
});
o.defineProperty(this, 'response', {
configurable: false,
get: function() {
if (!!~o.inArray(_p('responseType'), ['', 'text'])) {
if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) {
return '';
}
}
if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) {
return null;
}
return _p('response');
}
});
*/
function _p(prop, value) {
if (!props.hasOwnProperty(prop)) {
return;
}
if (arguments.length === 1) { // get
return Env.can('define_property') ? props[prop] : self[prop];
} else { // set
if (Env.can('define_property')) {
props[prop] = value;
} else {
self[prop] = value;
}
}
}
/*
function _toASCII(str, AllowUnassigned, UseSTD3ASCIIRules) {
// TODO: http://tools.ietf.org/html/rfc3490#section-4.1
return str.toLowerCase();
}
*/
function _doXHR(data) {
var self = this;
_start_time = new Date().getTime();
_xhr = new RuntimeTarget();
function loadEnd() {
if (_xhr) { // it could have been destroyed by now
_xhr.destroy();
_xhr = null;
}
self.dispatchEvent('loadend');
self = null;
}
function exec(runtime) {
_xhr.bind('LoadStart', function(e) {
_p('readyState', XMLHttpRequest.LOADING);
self.dispatchEvent('readystatechange');
self.dispatchEvent(e);
if (_upload_events_flag) {
self.upload.dispatchEvent(e);
}
});
_xhr.bind('Progress', function(e) {
if (_p('readyState') !== XMLHttpRequest.LOADING) {
_p('readyState', XMLHttpRequest.LOADING); // LoadStart unreliable (in Flash for example)
self.dispatchEvent('readystatechange');
}
self.dispatchEvent(e);
});
_xhr.bind('UploadProgress', function(e) {
if (_upload_events_flag) {
self.upload.dispatchEvent({
type: 'progress',
lengthComputable: false,
total: e.total,
loaded: e.loaded
});
}
});
_xhr.bind('Load', function(e) {
_p('readyState', XMLHttpRequest.DONE);
_p('status', Number(runtime.exec.call(_xhr, 'XMLHttpRequest', 'getStatus') || 0));
_p('statusText', httpCode[_p('status')] || "");
_p('response', runtime.exec.call(_xhr, 'XMLHttpRequest', 'getResponse', _p('responseType')));
if (!!~Basic.inArray(_p('responseType'), ['text', ''])) {
_p('responseText', _p('response'));
} else if (_p('responseType') === 'document') {
_p('responseXML', _p('response'));
}
_responseHeaders = runtime.exec.call(_xhr, 'XMLHttpRequest', 'getAllResponseHeaders');
self.dispatchEvent('readystatechange');
if (_p('status') > 0) { // status 0 usually means that server is unreachable
if (_upload_events_flag) {
self.upload.dispatchEvent(e);
}
self.dispatchEvent(e);
} else {
_error_flag = true;
self.dispatchEvent('error');
}
loadEnd();
});
_xhr.bind('Abort', function(e) {
self.dispatchEvent(e);
loadEnd();
});
_xhr.bind('Error', function(e) {
_error_flag = true;
_p('readyState', XMLHttpRequest.DONE);
self.dispatchEvent('readystatechange');
_upload_complete_flag = true;
self.dispatchEvent(e);
loadEnd();
});
runtime.exec.call(_xhr, 'XMLHttpRequest', 'send', {
url: _url,
method: _method,
async: _async,
user: _user,
password: _password,
headers: _headers,
mimeType: _mimeType,
encoding: _encoding,
responseType: self.responseType,
withCredentials: self.withCredentials,
options: _options
}, data);
}
// clarify our requirements
if (typeof(_options.required_caps) === 'string') {
_options.required_caps = Runtime.parseCaps(_options.required_caps);
}
_options.required_caps = Basic.extend({}, _options.required_caps, {
return_response_type: self.responseType
});
if (data instanceof FormData) {
_options.required_caps.send_multipart = true;
}
if (!_same_origin_flag) {
_options.required_caps.do_cors = true;
}
if (_options.ruid) { // we do not need to wait if we can connect directly
exec(_xhr.connectRuntime(_options));
} else {
_xhr.bind('RuntimeInit', function(e, runtime) {
exec(runtime);
});
_xhr.bind('RuntimeError', function(e, err) {
self.dispatchEvent('RuntimeError', err);
});
_xhr.connectRuntime(_options);
}
}
function _reset() {
_p('responseText', "");
_p('responseXML', null);
_p('response', null);
_p('status', 0);
_p('statusText', "");
_start_time = _timeoutset_time = null;
}
}
XMLHttpRequest.UNSENT = 0;
XMLHttpRequest.OPENED = 1;
XMLHttpRequest.HEADERS_RECEIVED = 2;
XMLHttpRequest.LOADING = 3;
XMLHttpRequest.DONE = 4;
XMLHttpRequest.prototype = EventTarget.instance;
return XMLHttpRequest;
});
// Included from: src/javascript/runtime/Transporter.js
/**
* Transporter.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
define("moxie/runtime/Transporter", [
"moxie/core/utils/Basic",
"moxie/core/utils/Encode",
"moxie/runtime/RuntimeClient",
"moxie/core/EventTarget"
], function(Basic, Encode, RuntimeClient, EventTarget) {
function Transporter() {
var mod, _runtime, _data, _size, _pos, _chunk_size;
RuntimeClient.call(this);
Basic.extend(this, {
uid: Basic.guid('uid_'),
state: Transporter.IDLE,
result: null,
transport: function(data, type, options) {
var self = this;
options = Basic.extend({
chunk_size: 204798
}, options);
// should divide by three, base64 requires this
if ((mod = options.chunk_size % 3)) {
options.chunk_size += 3 - mod;
}
_chunk_size = options.chunk_size;
_reset.call(this);
_data = data;
_size = data.length;
if (Basic.typeOf(options) === 'string' || options.ruid) {
_run.call(self, type, this.connectRuntime(options));
} else {
// we require this to run only once
var cb = function(e, runtime) {
self.unbind("RuntimeInit", cb);
_run.call(self, type, runtime);
};
this.bind("RuntimeInit", cb);
this.connectRuntime(options);
}
},
abort: function() {
var self = this;
self.state = Transporter.IDLE;
if (_runtime) {
_runtime.exec.call(self, 'Transporter', 'clear');
self.trigger("TransportingAborted");
}
_reset.call(self);
},
destroy: function() {
this.unbindAll();
_runtime = null;
this.disconnectRuntime();
_reset.call(this);
}
});
function _reset() {
_size = _pos = 0;
_data = this.result = null;
}
function _run(type, runtime) {
var self = this;
_runtime = runtime;
//self.unbind("RuntimeInit");
self.bind("TransportingProgress", function(e) {
_pos = e.loaded;
if (_pos < _size && Basic.inArray(self.state, [Transporter.IDLE, Transporter.DONE]) === -1) {
_transport.call(self);
}
}, 999);
self.bind("TransportingComplete", function() {
_pos = _size;
self.state = Transporter.DONE;
_data = null; // clean a bit
self.result = _runtime.exec.call(self, 'Transporter', 'getAsBlob', type || '');
}, 999);
self.state = Transporter.BUSY;
self.trigger("TransportingStarted");
_transport.call(self);
}
function _transport() {
var self = this,
chunk,
bytesLeft = _size - _pos;
if (_chunk_size > bytesLeft) {
_chunk_size = bytesLeft;
}
chunk = Encode.btoa(_data.substr(_pos, _chunk_size));
_runtime.exec.call(self, 'Transporter', 'receive', chunk, _size);
}
}
Transporter.IDLE = 0;
Transporter.BUSY = 1;
Transporter.DONE = 2;
Transporter.prototype = EventTarget.instance;
return Transporter;
});
// Included from: src/javascript/image/Image.js
/**
* Image.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
define("moxie/image/Image", [
"moxie/core/utils/Basic",
"moxie/core/utils/Dom",
"moxie/core/Exceptions",
"moxie/file/FileReaderSync",
"moxie/xhr/XMLHttpRequest",
"moxie/runtime/Runtime",
"moxie/runtime/RuntimeClient",
"moxie/runtime/Transporter",
"moxie/core/utils/Env",
"moxie/core/EventTarget",
"moxie/file/Blob",
"moxie/file/File",
"moxie/core/utils/Encode"
], function(Basic, Dom, x, FileReaderSync, XMLHttpRequest, Runtime, RuntimeClient, Transporter, Env, EventTarget, Blob, File, Encode) {
/**
Image preloading and manipulation utility. Additionally it provides access to image meta info (Exif, GPS) and raw binary data.
@class Image
@constructor
@extends EventTarget
*/
var dispatches = [
'progress',
/**
Dispatched when loading is complete.
@event load
@param {Object} event
*/
'load',
'error',
/**
Dispatched when resize operation is complete.
@event resize
@param {Object} event
*/
'resize',
/**
Dispatched when visual representation of the image is successfully embedded
into the corresponsing container.
@event embedded
@param {Object} event
*/
'embedded'
];
function Image() {
RuntimeClient.call(this);
Basic.extend(this, {
/**
Unique id of the component
@property uid
@type {String}
*/
uid: Basic.guid('uid_'),
/**
Unique id of the connected runtime, if any.
@property ruid
@type {String}
*/
ruid: null,
/**
Name of the file, that was used to create an image, if available. If not equals to empty string.
@property name
@type {String}
@default ""
*/
name: "",
/**
Size of the image in bytes. Actual value is set only after image is preloaded.
@property size
@type {Number}
@default 0
*/
size: 0,
/**
Width of the image. Actual value is set only after image is preloaded.
@property width
@type {Number}
@default 0
*/
width: 0,
/**
Height of the image. Actual value is set only after image is preloaded.
@property height
@type {Number}
@default 0
*/
height: 0,
/**
Mime type of the image. Currently only image/jpeg and image/png are supported. Actual value is set only after image is preloaded.
@property type
@type {String}
@default ""
*/
type: "",
/**
Holds meta info (Exif, GPS). Is populated only for image/jpeg. Actual value is set only after image is preloaded.
@property meta
@type {Object}
@default {}
*/
meta: {},
/**
Alias for load method, that takes another mOxie.Image object as a source (see load).
@method clone
@param {Image} src Source for the image
@param {Boolean} [exact=false] Whether to activate in-depth clone mode
*/
clone: function() {
this.load.apply(this, arguments);
},
/**
Loads image from various sources. Currently the source for new image can be: mOxie.Image, mOxie.Blob/mOxie.File,
native Blob/File, dataUrl or URL. Depending on the type of the source, arguments - differ. When source is URL,
Image will be downloaded from remote destination and loaded in memory.
@example
var img = new mOxie.Image();
img.onload = function() {
var blob = img.getAsBlob();
var formData = new mOxie.FormData();
formData.append('file', blob);
var xhr = new mOxie.XMLHttpRequest();
xhr.onload = function() {
// upload complete
};
xhr.open('post', 'upload.php');
xhr.send(formData);
};
img.load("http://www.moxiecode.com/images/mox-logo.jpg"); // notice file extension (.jpg)
@method load
@param {Image|Blob|File|String} src Source for the image
@param {Boolean|Object} [mixed]
*/
load: function() {
// this is here because to bind properly we need an uid first, which is created above
this.bind('Load Resize', function() {
_updateInfo.call(this);
}, 999);
this.convertEventPropsToHandlers(dispatches);
_load.apply(this, arguments);
},
/**
Downsizes the image to fit the specified width/height. If crop is supplied, image will be cropped to exact dimensions.
@method downsize
@param {Number} width Resulting width
@param {Number} [height=width] Resulting height (optional, if not supplied will default to width)
@param {Boolean} [crop=false] Whether to crop the image to exact dimensions
@param {Boolean} [preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize)
*/
downsize: function(opts) {
var defaults = {
width: this.width,
height: this.height,
crop: false,
preserveHeaders: true
};
if (typeof(opts) === 'object') {
opts = Basic.extend(defaults, opts);
} else {
opts = Basic.extend(defaults, {
width: arguments[0],
height: arguments[1],
crop: arguments[2],
preserveHeaders: arguments[3]
});
}
try {
if (!this.size) { // only preloaded image objects can be used as source
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
// no way to reliably intercept the crash due to high resolution, so we simply avoid it
if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) {
throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR);
}
this.getRuntime().exec.call(this, 'Image', 'downsize', opts.width, opts.height, opts.crop, opts.preserveHeaders);
} catch(ex) {
// for now simply trigger error event
this.trigger('error', ex.code);
}
},
/**
Alias for downsize(width, height, true). (see downsize)
@method crop
@param {Number} width Resulting width
@param {Number} [height=width] Resulting height (optional, if not supplied will default to width)
@param {Boolean} [preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize)
*/
crop: function(width, height, preserveHeaders) {
this.downsize(width, height, true, preserveHeaders);
},
getAsCanvas: function() {
if (!Env.can('create_canvas')) {
throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR);
}
var runtime = this.connectRuntime(this.ruid);
return runtime.exec.call(this, 'Image', 'getAsCanvas');
},
/**
Retrieves image in it's current state as mOxie.Blob object. Cannot be run on empty or image in progress (throws
DOMException.INVALID_STATE_ERR).
@method getAsBlob
@param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
@param {Number} [quality=90] Applicable only together with mime type image/jpeg
@return {Blob} Image as Blob
*/
getAsBlob: function(type, quality) {
if (!this.size) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
if (!type) {
type = 'image/jpeg';
}
if (type === 'image/jpeg' && !quality) {
quality = 90;
}
return this.getRuntime().exec.call(this, 'Image', 'getAsBlob', type, quality);
},
/**
Retrieves image in it's current state as dataURL string. Cannot be run on empty or image in progress (throws
DOMException.INVALID_STATE_ERR).
@method getAsDataURL
@param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
@param {Number} [quality=90] Applicable only together with mime type image/jpeg
@return {String} Image as dataURL string
*/
getAsDataURL: function(type, quality) {
if (!this.size) {
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
return this.getRuntime().exec.call(this, 'Image', 'getAsDataURL', type, quality);
},
/**
Retrieves image in it's current state as binary string. Cannot be run on empty or image in progress (throws
DOMException.INVALID_STATE_ERR).
@method getAsBinaryString
@param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png
@param {Number} [quality=90] Applicable only together with mime type image/jpeg
@return {String} Image as binary string
*/
getAsBinaryString: function(type, quality) {
var dataUrl = this.getAsDataURL(type, quality);
return Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7));
},
/**
Embeds a visual representation of the image into the specified node. Depending on the runtime,
it might be a canvas, an img node or a thrid party shim object (Flash or SilverLight - very rare,
can be used in legacy browsers that do not have canvas or proper dataURI support).
@method embed
@param {DOMElement} el DOM element to insert the image object into
@param {Object} [options]
@param {Number} [options.width] The width of an embed (defaults to the image width)
@param {Number} [options.height] The height of an embed (defaults to the image height)
@param {String} [type="image/jpeg"] Mime type
@param {Number} [quality=90] Quality of an embed, if mime type is image/jpeg
@param {Boolean} [crop=false] Whether to crop an embed to the specified dimensions
*/
embed: function(el) {
var self = this
, imgCopy
, type, quality, crop
, options = arguments[1] || {}
, width = this.width
, height = this.height
, runtime // this has to be outside of all the closures to contain proper runtime
;
function onResize() {
// if possible, embed a canvas element directly
if (Env.can('create_canvas')) {
var canvas = imgCopy.getAsCanvas();
if (canvas) {
el.appendChild(canvas);
canvas = null;
imgCopy.destroy();
self.trigger('embedded');
return;
}
}
var dataUrl = imgCopy.getAsDataURL(type, quality);
if (!dataUrl) {
throw new x.ImageError(x.ImageError.WRONG_FORMAT);
}
if (Env.can('use_data_uri_of', dataUrl.length)) {
el.innerHTML = '';
imgCopy.destroy();
self.trigger('embedded');
} else {
var tr = new Transporter();
tr.bind("TransportingComplete", function() {
runtime = self.connectRuntime(this.result.ruid);
self.bind("Embedded", function() {
// position and size properly
Basic.extend(runtime.getShimContainer().style, {
//position: 'relative',
top: '0px',
left: '0px',
width: imgCopy.width + 'px',
height: imgCopy.height + 'px'
});
// some shims (Flash/SilverLight) reinitialize, if parent element is hidden, reordered or it's
// position type changes (in Gecko), but since we basically need this only in IEs 6/7 and
// sometimes 8 and they do not have this problem, we can comment this for now
/*tr.bind("RuntimeInit", function(e, runtime) {
tr.destroy();
runtime.destroy();
onResize.call(self); // re-feed our image data
});*/
runtime = null;
}, 999);
runtime.exec.call(self, "ImageView", "display", this.result.uid, width, height);
imgCopy.destroy();
});
tr.transport(Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7)), type, Basic.extend({}, options, {
required_caps: {
display_media: true
},
runtime_order: 'flash,silverlight',
container: el
}));
}
}
try {
if (!(el = Dom.get(el))) {
throw new x.DOMException(x.DOMException.INVALID_NODE_TYPE_ERR);
}
if (!this.size) { // only preloaded image objects can be used as source
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) {
throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR);
}
type = options.type || this.type || 'image/jpeg';
quality = options.quality || 90;
crop = Basic.typeOf(options.crop) !== 'undefined' ? options.crop : false;
// figure out dimensions for the thumb
if (options.width) {
width = options.width;
height = options.height || width;
} else {
// if container element has measurable dimensions, use them
var dimensions = Dom.getSize(el);
if (dimensions.w && dimensions.h) { // both should be > 0
width = dimensions.w;
height = dimensions.h;
}
}
imgCopy = new Image();
imgCopy.bind("Resize", function() {
onResize.call(self);
});
imgCopy.bind("Load", function() {
imgCopy.downsize(width, height, crop, false);
});
imgCopy.clone(this, false);
return imgCopy;
} catch(ex) {
// for now simply trigger error event
this.trigger('error', ex.code);
}
},
/**
Properly destroys the image and frees resources in use. If any. Recommended way to dispose mOxie.Image object.
@method destroy
*/
destroy: function() {
if (this.ruid) {
this.getRuntime().exec.call(this, 'Image', 'destroy');
this.disconnectRuntime();
}
this.unbindAll();
}
});
function _updateInfo(info) {
if (!info) {
info = this.getRuntime().exec.call(this, 'Image', 'getInfo');
}
this.size = info.size;
this.width = info.width;
this.height = info.height;
this.type = info.type;
this.meta = info.meta;
// update file name, only if empty
if (this.name === '') {
this.name = info.name;
}
}
function _load(src) {
var srcType = Basic.typeOf(src);
try {
// if source is Image
if (src instanceof Image) {
if (!src.size) { // only preloaded image objects can be used as source
throw new x.DOMException(x.DOMException.INVALID_STATE_ERR);
}
_loadFromImage.apply(this, arguments);
}
// if source is o.Blob/o.File
else if (src instanceof Blob) {
if (!~Basic.inArray(src.type, ['image/jpeg', 'image/png'])) {
throw new x.ImageError(x.ImageError.WRONG_FORMAT);
}
_loadFromBlob.apply(this, arguments);
}
// if native blob/file
else if (Basic.inArray(srcType, ['blob', 'file']) !== -1) {
_load.call(this, new File(null, src), arguments[1]);
}
// if String
else if (srcType === 'string') {
// if dataUrl String
if (/^data:[^;]*;base64,/.test(src)) {
_load.call(this, new Blob(null, { data: src }), arguments[1]);
}
// else assume Url, either relative or absolute
else {
_loadFromUrl.apply(this, arguments);
}
}
// if source seems to be an img node
else if (srcType === 'node' && src.nodeName.toLowerCase() === 'img') {
_load.call(this, src.src, arguments[1]);
}
else {
throw new x.DOMException(x.DOMException.TYPE_MISMATCH_ERR);
}
} catch(ex) {
// for now simply trigger error event
this.trigger('error', ex.code);
}
}
function _loadFromImage(img, exact) {
var runtime = this.connectRuntime(img.ruid);
this.ruid = runtime.uid;
runtime.exec.call(this, 'Image', 'loadFromImage', img, (Basic.typeOf(exact) === 'undefined' ? true : exact));
}
function _loadFromBlob(blob, options) {
var self = this;
self.name = blob.name || '';
function exec(runtime) {
self.ruid = runtime.uid;
runtime.exec.call(self, 'Image', 'loadFromBlob', blob);
}
if (blob.isDetached()) {
this.bind('RuntimeInit', function(e, runtime) {
exec(runtime);
});
// convert to object representation
if (options && typeof(options.required_caps) === 'string') {
options.required_caps = Runtime.parseCaps(options.required_caps);
}
this.connectRuntime(Basic.extend({
required_caps: {
access_image_binary: true,
resize_image: true
}
}, options));
} else {
exec(this.connectRuntime(blob.ruid));
}
}
function _loadFromUrl(url, options) {
var self = this, xhr;
xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.responseType = 'blob';
xhr.onprogress = function(e) {
self.trigger(e);
};
xhr.onload = function() {
_loadFromBlob.call(self, xhr.response, true);
};
xhr.onerror = function(e) {
self.trigger(e);
};
xhr.onloadend = function() {
xhr.destroy();
};
xhr.bind('RuntimeError', function(e, err) {
self.trigger('RuntimeError', err);
});
xhr.send(null, options);
}
}
// virtual world will crash on you if image has a resolution higher than this:
Image.MAX_RESIZE_WIDTH = 6500;
Image.MAX_RESIZE_HEIGHT = 6500;
Image.prototype = EventTarget.instance;
return Image;
});
// Included from: src/javascript/runtime/html5/Runtime.js
/**
* Runtime.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/*global File:true */
/**
Defines constructor for HTML5 runtime.
@class moxie/runtime/html5/Runtime
@private
*/
define("moxie/runtime/html5/Runtime", [
"moxie/core/utils/Basic",
"moxie/core/Exceptions",
"moxie/runtime/Runtime",
"moxie/core/utils/Env"
], function(Basic, x, Runtime, Env) {
var type = "html5", extensions = {};
function Html5Runtime(options) {
var I = this
, Test = Runtime.capTest
, True = Runtime.capTrue
;
var caps = Basic.extend({
access_binary: Test(window.FileReader || window.File && window.File.getAsDataURL),
access_image_binary: function() {
return I.can('access_binary') && !!extensions.Image;
},
display_media: Test(Env.can('create_canvas') || Env.can('use_data_uri_over32kb')),
do_cors: Test(window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()),
drag_and_drop: Test(function() {
// this comes directly from Modernizr: http://www.modernizr.com/
var div = document.createElement('div');
// IE has support for drag and drop since version 5, but doesn't support dropping files from desktop
return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && (Env.browser !== 'IE' || Env.version > 9);
}()),
filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest
return (Env.browser === 'Chrome' && Env.version >= 28) || (Env.browser === 'IE' && Env.version >= 10);
}()),
return_response_headers: True,
return_response_type: function(responseType) {
if (responseType === 'json' && !!window.JSON) { // we can fake this one even if it's not supported
return true;
}
return Env.can('return_response_type', responseType);
},
return_status_code: True,
report_upload_progress: Test(window.XMLHttpRequest && new XMLHttpRequest().upload),
resize_image: function() {
return I.can('access_binary') && Env.can('create_canvas');
},
select_file: function() {
return Env.can('use_fileinput') && window.File;
},
select_folder: function() {
return I.can('select_file') && Env.browser === 'Chrome' && Env.version >= 21;
},
select_multiple: function() {
// it is buggy on Safari Windows and iOS
return I.can('select_file') &&
!(Env.browser === 'Safari' && Env.os === 'Windows') &&
!(Env.os === 'iOS' && Env.verComp(Env.osVersion, "7.0.4", '<'));
},
send_binary_string: Test(window.XMLHttpRequest && (new XMLHttpRequest().sendAsBinary || (window.Uint8Array && window.ArrayBuffer))),
send_custom_headers: Test(window.XMLHttpRequest),
send_multipart: function() {
return !!(window.XMLHttpRequest && new XMLHttpRequest().upload && window.FormData) || I.can('send_binary_string');
},
slice_blob: Test(window.File && (File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice)),
stream_upload: function(){
return I.can('slice_blob') && I.can('send_multipart');
},
summon_file_dialog: Test(function() { // yeah... some dirty sniffing here...
return (Env.browser === 'Firefox' && Env.version >= 4) ||
(Env.browser === 'Opera' && Env.version >= 12) ||
(Env.browser === 'IE' && Env.version >= 10) ||
!!~Basic.inArray(Env.browser, ['Chrome', 'Safari']);
}()),
upload_filesize: True
},
arguments[2]
);
Runtime.call(this, options, (arguments[1] || type), caps);
Basic.extend(this, {
init : function() {
this.trigger("Init");
},
destroy: (function(destroy) { // extend default destroy method
return function() {
destroy.call(I);
destroy = I = null;
};
}(this.destroy))
});
Basic.extend(this.getShim(), extensions);
}
Runtime.addConstructor(type, Html5Runtime);
return extensions;
});
// Included from: src/javascript/runtime/html5/file/Blob.js
/**
* Blob.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/file/Blob
@private
*/
define("moxie/runtime/html5/file/Blob", [
"moxie/runtime/html5/Runtime",
"moxie/file/Blob"
], function(extensions, Blob) {
function HTML5Blob() {
function w3cBlobSlice(blob, start, end) {
var blobSlice;
if (window.File.prototype.slice) {
try {
blob.slice(); // depricated version will throw WRONG_ARGUMENTS_ERR exception
return blob.slice(start, end);
} catch (e) {
// depricated slice method
return blob.slice(start, end - start);
}
// slice method got prefixed: https://bugzilla.mozilla.org/show_bug.cgi?id=649672
} else if ((blobSlice = window.File.prototype.webkitSlice || window.File.prototype.mozSlice)) {
return blobSlice.call(blob, start, end);
} else {
return null; // or throw some exception
}
}
this.slice = function() {
return new Blob(this.getRuntime().uid, w3cBlobSlice.apply(this, arguments));
};
}
return (extensions.Blob = HTML5Blob);
});
// Included from: src/javascript/core/utils/Events.js
/**
* Events.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
define('moxie/core/utils/Events', [
'moxie/core/utils/Basic'
], function(Basic) {
var eventhash = {}, uid = 'moxie_' + Basic.guid();
// IE W3C like event funcs
function preventDefault() {
this.returnValue = false;
}
function stopPropagation() {
this.cancelBubble = true;
}
/**
Adds an event handler to the specified object and store reference to the handler
in objects internal Plupload registry (@see removeEvent).
@method addEvent
@for Utils
@static
@param {Object} obj DOM element like object to add handler to.
@param {String} name Name to add event listener to.
@param {Function} callback Function to call when event occurs.
@param {String} [key] that might be used to add specifity to the event record.
*/
var addEvent = function(obj, name, callback, key) {
var func, events;
name = name.toLowerCase();
// Add event listener
if (obj.addEventListener) {
func = callback;
obj.addEventListener(name, func, false);
} else if (obj.attachEvent) {
func = function() {
var evt = window.event;
if (!evt.target) {
evt.target = evt.srcElement;
}
evt.preventDefault = preventDefault;
evt.stopPropagation = stopPropagation;
callback(evt);
};
obj.attachEvent('on' + name, func);
}
// Log event handler to objects internal mOxie registry
if (!obj[uid]) {
obj[uid] = Basic.guid();
}
if (!eventhash.hasOwnProperty(obj[uid])) {
eventhash[obj[uid]] = {};
}
events = eventhash[obj[uid]];
if (!events.hasOwnProperty(name)) {
events[name] = [];
}
events[name].push({
func: func,
orig: callback, // store original callback for IE
key: key
});
};
/**
Remove event handler from the specified object. If third argument (callback)
is not specified remove all events with the specified name.
@method removeEvent
@static
@param {Object} obj DOM element to remove event listener(s) from.
@param {String} name Name of event listener to remove.
@param {Function|String} [callback] might be a callback or unique key to match.
*/
var removeEvent = function(obj, name, callback) {
var type, undef;
name = name.toLowerCase();
if (obj[uid] && eventhash[obj[uid]] && eventhash[obj[uid]][name]) {
type = eventhash[obj[uid]][name];
} else {
return;
}
for (var i = type.length - 1; i >= 0; i--) {
// undefined or not, key should match
if (type[i].orig === callback || type[i].key === callback) {
if (obj.removeEventListener) {
obj.removeEventListener(name, type[i].func, false);
} else if (obj.detachEvent) {
obj.detachEvent('on'+name, type[i].func);
}
type[i].orig = null;
type[i].func = null;
type.splice(i, 1);
// If callback was passed we are done here, otherwise proceed
if (callback !== undef) {
break;
}
}
}
// If event array got empty, remove it
if (!type.length) {
delete eventhash[obj[uid]][name];
}
// If mOxie registry has become empty, remove it
if (Basic.isEmptyObj(eventhash[obj[uid]])) {
delete eventhash[obj[uid]];
// IE doesn't let you remove DOM object property with - delete
try {
delete obj[uid];
} catch(e) {
obj[uid] = undef;
}
}
};
/**
Remove all kind of events from the specified object
@method removeAllEvents
@static
@param {Object} obj DOM element to remove event listeners from.
@param {String} [key] unique key to match, when removing events.
*/
var removeAllEvents = function(obj, key) {
if (!obj || !obj[uid]) {
return;
}
Basic.each(eventhash[obj[uid]], function(events, name) {
removeEvent(obj, name, key);
});
};
return {
addEvent: addEvent,
removeEvent: removeEvent,
removeAllEvents: removeAllEvents
};
});
// Included from: src/javascript/runtime/html5/file/FileInput.js
/**
* FileInput.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/file/FileInput
@private
*/
define("moxie/runtime/html5/file/FileInput", [
"moxie/runtime/html5/Runtime",
"moxie/core/utils/Basic",
"moxie/core/utils/Dom",
"moxie/core/utils/Events",
"moxie/core/utils/Mime",
"moxie/core/utils/Env"
], function(extensions, Basic, Dom, Events, Mime, Env) {
function FileInput() {
var _files = [], _options;
Basic.extend(this, {
init: function(options) {
var comp = this, I = comp.getRuntime(), input, shimContainer, mimes, browseButton, zIndex, top;
_options = options;
_files = [];
// figure out accept string
mimes = _options.accept.mimes || Mime.extList2mimes(_options.accept, I.can('filter_by_extension'));
shimContainer = I.getShimContainer();
shimContainer.innerHTML = '';
input = Dom.get(I.uid);
// prepare file input to be placed underneath the browse_button element
Basic.extend(input.style, {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%'
});
browseButton = Dom.get(_options.browse_button);
// Route click event to the input[type=file] element for browsers that support such behavior
if (I.can('summon_file_dialog')) {
if (Dom.getStyle(browseButton, 'position') === 'static') {
browseButton.style.position = 'relative';
}
zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1;
browseButton.style.zIndex = zIndex;
shimContainer.style.zIndex = zIndex - 1;
Events.addEvent(browseButton, 'click', function(e) {
var input = Dom.get(I.uid);
if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file]
input.click();
}
e.preventDefault();
}, comp.uid);
}
/* Since we have to place input[type=file] on top of the browse_button for some browsers,
browse_button loses interactivity, so we restore it here */
top = I.can('summon_file_dialog') ? browseButton : shimContainer;
Events.addEvent(top, 'mouseover', function() {
comp.trigger('mouseenter');
}, comp.uid);
Events.addEvent(top, 'mouseout', function() {
comp.trigger('mouseleave');
}, comp.uid);
Events.addEvent(top, 'mousedown', function() {
comp.trigger('mousedown');
}, comp.uid);
Events.addEvent(Dom.get(_options.container), 'mouseup', function() {
comp.trigger('mouseup');
}, comp.uid);
input.onchange = function onChange() { // there should be only one handler for this
_files = [];
if (_options.directory) {
// folders are represented by dots, filter them out (Chrome 11+)
Basic.each(this.files, function(file) {
if (file.name !== ".") { // if it doesn't looks like a folder
_files.push(file);
}
});
} else {
_files = [].slice.call(this.files);
}
// clearing the value enables the user to select the same file again if they want to
if (Env.browser !== 'IE' && Env.browser !== 'IEMobile') {
this.value = '';
} else {
// in IE input[type="file"] is read-only so the only way to reset it is to re-insert it
var clone = this.cloneNode(true);
this.parentNode.replaceChild(clone, this);
clone.onchange = onChange;
}
comp.trigger('change');
};
// ready event is perfectly asynchronous
comp.trigger({
type: 'ready',
async: true
});
shimContainer = null;
},
getFiles: function() {
return _files;
},
disable: function(state) {
var I = this.getRuntime(), input;
if ((input = Dom.get(I.uid))) {
input.disabled = !!state;
}
},
destroy: function() {
var I = this.getRuntime()
, shim = I.getShim()
, shimContainer = I.getShimContainer()
;
Events.removeAllEvents(shimContainer, this.uid);
Events.removeAllEvents(_options && Dom.get(_options.container), this.uid);
Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid);
if (shimContainer) {
shimContainer.innerHTML = '';
}
shim.removeInstance(this.uid);
_files = _options = shimContainer = shim = null;
}
});
}
return (extensions.FileInput = FileInput);
});
// Included from: src/javascript/runtime/html5/file/FileDrop.js
/**
* FileDrop.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/file/FileDrop
@private
*/
define("moxie/runtime/html5/file/FileDrop", [
"moxie/runtime/html5/Runtime",
"moxie/core/utils/Basic",
"moxie/core/utils/Dom",
"moxie/core/utils/Events",
"moxie/core/utils/Mime"
], function(extensions, Basic, Dom, Events, Mime) {
function FileDrop() {
var _files = [], _allowedExts = [], _options;
Basic.extend(this, {
init: function(options) {
var comp = this, dropZone;
_options = options;
_allowedExts = _extractExts(_options.accept);
dropZone = _options.container;
Events.addEvent(dropZone, 'dragover', function(e) {
if (!_hasFiles(e)) {
return;
}
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
}, comp.uid);
Events.addEvent(dropZone, 'drop', function(e) {
if (!_hasFiles(e)) {
return;
}
e.preventDefault();
_files = [];
// Chrome 21+ accepts folders via Drag'n'Drop
if (e.dataTransfer.items && e.dataTransfer.items[0].webkitGetAsEntry) {
_readItems(e.dataTransfer.items, function() {
comp.trigger("drop");
});
} else {
Basic.each(e.dataTransfer.files, function(file) {
if (_isAcceptable(file)) {
_files.push(file);
}
});
comp.trigger("drop");
}
}, comp.uid);
Events.addEvent(dropZone, 'dragenter', function(e) {
comp.trigger("dragenter");
}, comp.uid);
Events.addEvent(dropZone, 'dragleave', function(e) {
comp.trigger("dragleave");
}, comp.uid);
},
getFiles: function() {
return _files;
},
destroy: function() {
Events.removeAllEvents(_options && Dom.get(_options.container), this.uid);
_files = _allowedExts = _options = null;
}
});
function _hasFiles(e) {
if (!e.dataTransfer || !e.dataTransfer.types) { // e.dataTransfer.files is not available in Gecko during dragover
return false;
}
var types = Basic.toArray(e.dataTransfer.types || []);
return Basic.inArray("Files", types) !== -1 ||
Basic.inArray("public.file-url", types) !== -1 || // Safari < 5
Basic.inArray("application/x-moz-file", types) !== -1 // Gecko < 1.9.2 (< Firefox 3.6)
;
}
function _extractExts(accept) {
var exts = [];
for (var i = 0; i < accept.length; i++) {
[].push.apply(exts, accept[i].extensions.split(/\s*,\s*/));
}
return Basic.inArray('*', exts) === -1 ? exts : [];
}
function _isAcceptable(file) {
if (!_allowedExts.length) {
return true;
}
var ext = Mime.getFileExtension(file.name);
return !ext || Basic.inArray(ext, _allowedExts) !== -1;
}
function _readItems(items, cb) {
var entries = [];
Basic.each(items, function(item) {
var entry = item.webkitGetAsEntry();
// Address #998 (https://code.google.com/p/chromium/issues/detail?id=332579)
if (entry) {
// file() fails on OSX when the filename contains a special character (e.g. umlaut): see #61
if (entry.isFile) {
var file = item.getAsFile();
if (_isAcceptable(file)) {
_files.push(file);
}
} else {
entries.push(entry);
}
}
});
if (entries.length) {
_readEntries(entries, cb);
} else {
cb();
}
}
function _readEntries(entries, cb) {
var queue = [];
Basic.each(entries, function(entry) {
queue.push(function(cbcb) {
_readEntry(entry, cbcb);
});
});
Basic.inSeries(queue, function() {
cb();
});
}
function _readEntry(entry, cb) {
if (entry.isFile) {
entry.file(function(file) {
if (_isAcceptable(file)) {
_files.push(file);
}
cb();
}, function() {
// fire an error event maybe
cb();
});
} else if (entry.isDirectory) {
_readDirEntry(entry, cb);
} else {
cb(); // not file, not directory? what then?..
}
}
function _readDirEntry(dirEntry, cb) {
var entries = [], dirReader = dirEntry.createReader();
// keep quering recursively till no more entries
function getEntries(cbcb) {
dirReader.readEntries(function(moreEntries) {
if (moreEntries.length) {
[].push.apply(entries, moreEntries);
getEntries(cbcb);
} else {
cbcb();
}
}, cbcb);
}
// ...and you thought FileReader was crazy...
getEntries(function() {
_readEntries(entries, cb);
});
}
}
return (extensions.FileDrop = FileDrop);
});
// Included from: src/javascript/runtime/html5/file/FileReader.js
/**
* FileReader.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/file/FileReader
@private
*/
define("moxie/runtime/html5/file/FileReader", [
"moxie/runtime/html5/Runtime",
"moxie/core/utils/Encode",
"moxie/core/utils/Basic"
], function(extensions, Encode, Basic) {
function FileReader() {
var _fr, _convertToBinary = false;
Basic.extend(this, {
read: function(op, blob) {
var target = this;
_fr = new window.FileReader();
_fr.addEventListener('progress', function(e) {
target.trigger(e);
});
_fr.addEventListener('load', function(e) {
target.trigger(e);
});
_fr.addEventListener('error', function(e) {
target.trigger(e, _fr.error);
});
_fr.addEventListener('loadend', function() {
_fr = null;
});
if (Basic.typeOf(_fr[op]) === 'function') {
_convertToBinary = false;
_fr[op](blob.getSource());
} else if (op === 'readAsBinaryString') { // readAsBinaryString is depricated in general and never existed in IE10+
_convertToBinary = true;
_fr.readAsDataURL(blob.getSource());
}
},
getResult: function() {
return _fr && _fr.result ? (_convertToBinary ? _toBinary(_fr.result) : _fr.result) : null;
},
abort: function() {
if (_fr) {
_fr.abort();
}
},
destroy: function() {
_fr = null;
}
});
function _toBinary(str) {
return Encode.atob(str.substring(str.indexOf('base64,') + 7));
}
}
return (extensions.FileReader = FileReader);
});
// Included from: src/javascript/runtime/html5/xhr/XMLHttpRequest.js
/**
* XMLHttpRequest.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/*global ActiveXObject:true */
/**
@class moxie/runtime/html5/xhr/XMLHttpRequest
@private
*/
define("moxie/runtime/html5/xhr/XMLHttpRequest", [
"moxie/runtime/html5/Runtime",
"moxie/core/utils/Basic",
"moxie/core/utils/Mime",
"moxie/core/utils/Url",
"moxie/file/File",
"moxie/file/Blob",
"moxie/xhr/FormData",
"moxie/core/Exceptions",
"moxie/core/utils/Env"
], function(extensions, Basic, Mime, Url, File, Blob, FormData, x, Env) {
function XMLHttpRequest() {
var self = this
, _xhr
, _filename
;
Basic.extend(this, {
send: function(meta, data) {
var target = this
, isGecko2_5_6 = (Env.browser === 'Mozilla' && Env.version >= 4 && Env.version < 7)
, isAndroidBrowser = Env.browser === 'Android Browser'
, mustSendAsBinary = false
;
// extract file name
_filename = meta.url.replace(/^.+?\/([\w\-\.]+)$/, '$1').toLowerCase();
_xhr = _getNativeXHR();
_xhr.open(meta.method, meta.url, meta.async, meta.user, meta.password);
// prepare data to be sent
if (data instanceof Blob) {
if (data.isDetached()) {
mustSendAsBinary = true;
}
data = data.getSource();
} else if (data instanceof FormData) {
if (data.hasBlob()) {
if (data.getBlob().isDetached()) {
data = _prepareMultipart.call(target, data); // _xhr must be instantiated and be in OPENED state
mustSendAsBinary = true;
} else if ((isGecko2_5_6 || isAndroidBrowser) && Basic.typeOf(data.getBlob().getSource()) === 'blob' && window.FileReader) {
// Gecko 2/5/6 can't send blob in FormData: https://bugzilla.mozilla.org/show_bug.cgi?id=649150
// Android browsers (default one and Dolphin) seem to have the same issue, see: #613
_preloadAndSend.call(target, meta, data);
return; // _preloadAndSend will reinvoke send() with transmutated FormData =%D
}
}
// transfer fields to real FormData
if (data instanceof FormData) { // if still a FormData, e.g. not mangled by _prepareMultipart()
var fd = new window.FormData();
data.each(function(value, name) {
if (value instanceof Blob) {
fd.append(name, value.getSource());
} else {
fd.append(name, value);
}
});
data = fd;
}
}
// if XHR L2
if (_xhr.upload) {
if (meta.withCredentials) {
_xhr.withCredentials = true;
}
_xhr.addEventListener('load', function(e) {
target.trigger(e);
});
_xhr.addEventListener('error', function(e) {
target.trigger(e);
});
// additionally listen to progress events
_xhr.addEventListener('progress', function(e) {
target.trigger(e);
});
_xhr.upload.addEventListener('progress', function(e) {
target.trigger({
type: 'UploadProgress',
loaded: e.loaded,
total: e.total
});
});
// ... otherwise simulate XHR L2
} else {
_xhr.onreadystatechange = function onReadyStateChange() {
// fake Level 2 events
switch (_xhr.readyState) {
case 1: // XMLHttpRequest.OPENED
// readystatechanged is fired twice for OPENED state (in IE and Mozilla) - neu
break;
// looks like HEADERS_RECEIVED (state 2) is not reported in Opera (or it's old versions) - neu
case 2: // XMLHttpRequest.HEADERS_RECEIVED
break;
case 3: // XMLHttpRequest.LOADING
// try to fire progress event for not XHR L2
var total, loaded;
try {
if (Url.hasSameOrigin(meta.url)) { // Content-Length not accessible for cross-domain on some browsers
total = _xhr.getResponseHeader('Content-Length') || 0; // old Safari throws an exception here
}
if (_xhr.responseText) { // responseText was introduced in IE7
loaded = _xhr.responseText.length;
}
} catch(ex) {
total = loaded = 0;
}
target.trigger({
type: 'progress',
lengthComputable: !!total,
total: parseInt(total, 10),
loaded: loaded
});
break;
case 4: // XMLHttpRequest.DONE
// release readystatechange handler (mostly for IE)
_xhr.onreadystatechange = function() {};
// usually status 0 is returned when server is unreachable, but FF also fails to status 0 for 408 timeout
if (_xhr.status === 0) {
target.trigger('error');
} else {
target.trigger('load');
}
break;
}
};
}
// set request headers
if (!Basic.isEmptyObj(meta.headers)) {
Basic.each(meta.headers, function(value, header) {
_xhr.setRequestHeader(header, value);
});
}
// request response type
if ("" !== meta.responseType && 'responseType' in _xhr) {
if ('json' === meta.responseType && !Env.can('return_response_type', 'json')) { // we can fake this one
_xhr.responseType = 'text';
} else {
_xhr.responseType = meta.responseType;
}
}
// send ...
if (!mustSendAsBinary) {
_xhr.send(data);
} else {
if (_xhr.sendAsBinary) { // Gecko
_xhr.sendAsBinary(data);
} else { // other browsers having support for typed arrays
(function() {
// mimic Gecko's sendAsBinary
var ui8a = new Uint8Array(data.length);
for (var i = 0; i < data.length; i++) {
ui8a[i] = (data.charCodeAt(i) & 0xff);
}
_xhr.send(ui8a.buffer);
}());
}
}
target.trigger('loadstart');
},
getStatus: function() {
// according to W3C spec it should return 0 for readyState < 3, but instead it throws an exception
try {
if (_xhr) {
return _xhr.status;
}
} catch(ex) {}
return 0;
},
getResponse: function(responseType) {
var I = this.getRuntime();
try {
switch (responseType) {
case 'blob':
var file = new File(I.uid, _xhr.response);
// try to extract file name from content-disposition if possible (might be - not, if CORS for example)
var disposition = _xhr.getResponseHeader('Content-Disposition');
if (disposition) {
// extract filename from response header if available
var match = disposition.match(/filename=([\'\"'])([^\1]+)\1/);
if (match) {
_filename = match[2];
}
}
file.name = _filename;
// pre-webkit Opera doesn't set type property on the blob response
if (!file.type) {
file.type = Mime.getFileMime(_filename);
}
return file;
case 'json':
if (!Env.can('return_response_type', 'json')) {
return _xhr.status === 200 && !!window.JSON ? JSON.parse(_xhr.responseText) : null;
}
return _xhr.response;
case 'document':
return _getDocument(_xhr);
default:
return _xhr.responseText !== '' ? _xhr.responseText : null; // against the specs, but for consistency across the runtimes
}
} catch(ex) {
return null;
}
},
getAllResponseHeaders: function() {
try {
return _xhr.getAllResponseHeaders();
} catch(ex) {}
return '';
},
abort: function() {
if (_xhr) {
_xhr.abort();
}
},
destroy: function() {
self = _filename = null;
}
});
// here we go... ugly fix for ugly bug
function _preloadAndSend(meta, data) {
var target = this, blob, fr;
// get original blob
blob = data.getBlob().getSource();
// preload blob in memory to be sent as binary string
fr = new window.FileReader();
fr.onload = function() {
// overwrite original blob
data.append(data.getBlobName(), new Blob(null, {
type: blob.type,
data: fr.result
}));
// invoke send operation again
self.send.call(target, meta, data);
};
fr.readAsBinaryString(blob);
}
function _getNativeXHR() {
if (window.XMLHttpRequest && !(Env.browser === 'IE' && Env.version < 8)) { // IE7 has native XHR but it's buggy
return new window.XMLHttpRequest();
} else {
return (function() {
var progIDs = ['Msxml2.XMLHTTP.6.0', 'Microsoft.XMLHTTP']; // if 6.0 available, use it, otherwise failback to default 3.0
for (var i = 0; i < progIDs.length; i++) {
try {
return new ActiveXObject(progIDs[i]);
} catch (ex) {}
}
})();
}
}
// @credits Sergey Ilinsky (http://www.ilinsky.com/)
function _getDocument(xhr) {
var rXML = xhr.responseXML;
var rText = xhr.responseText;
// Try parsing responseText (@see: http://www.ilinsky.com/articles/XMLHttpRequest/#bugs-ie-responseXML-content-type)
if (Env.browser === 'IE' && rText && rXML && !rXML.documentElement && /[^\/]+\/[^\+]+\+xml/.test(xhr.getResponseHeader("Content-Type"))) {
rXML = new window.ActiveXObject("Microsoft.XMLDOM");
rXML.async = false;
rXML.validateOnParse = false;
rXML.loadXML(rText);
}
// Check if there is no error in document
if (rXML) {
if ((Env.browser === 'IE' && rXML.parseError !== 0) || !rXML.documentElement || rXML.documentElement.tagName === "parsererror") {
return null;
}
}
return rXML;
}
function _prepareMultipart(fd) {
var boundary = '----moxieboundary' + new Date().getTime()
, dashdash = '--'
, crlf = '\r\n'
, multipart = ''
, I = this.getRuntime()
;
if (!I.can('send_binary_string')) {
throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR);
}
_xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
// append multipart parameters
fd.each(function(value, name) {
// Firefox 3.6 failed to convert multibyte characters to UTF-8 in sendAsBinary(),
// so we try it here ourselves with: unescape(encodeURIComponent(value))
if (value instanceof Blob) {
// Build RFC2388 blob
multipart += dashdash + boundary + crlf +
'Content-Disposition: form-data; name="' + name + '"; filename="' + unescape(encodeURIComponent(value.name || 'blob')) + '"' + crlf +
'Content-Type: ' + (value.type || 'application/octet-stream') + crlf + crlf +
value.getSource() + crlf;
} else {
multipart += dashdash + boundary + crlf +
'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf +
unescape(encodeURIComponent(value)) + crlf;
}
});
multipart += dashdash + boundary + dashdash + crlf;
return multipart;
}
}
return (extensions.XMLHttpRequest = XMLHttpRequest);
});
// Included from: src/javascript/runtime/html5/utils/BinaryReader.js
/**
* BinaryReader.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/utils/BinaryReader
@private
*/
define("moxie/runtime/html5/utils/BinaryReader", [], function() {
return function() {
var II = false, bin;
// Private functions
function read(idx, size) {
var mv = II ? 0 : -8 * (size - 1), sum = 0, i;
for (i = 0; i < size; i++) {
sum |= (bin.charCodeAt(idx + i) << Math.abs(mv + i*8));
}
return sum;
}
function putstr(segment, idx, length) {
length = arguments.length === 3 ? length : bin.length - idx - 1;
bin = bin.substr(0, idx) + segment + bin.substr(length + idx);
}
function write(idx, num, size) {
var str = '', mv = II ? 0 : -8 * (size - 1), i;
for (i = 0; i < size; i++) {
str += String.fromCharCode((num >> Math.abs(mv + i*8)) & 255);
}
putstr(str, idx, size);
}
// Public functions
return {
II: function(order) {
if (order === undefined) {
return II;
} else {
II = order;
}
},
init: function(binData) {
II = false;
bin = binData;
},
SEGMENT: function(idx, length, segment) {
switch (arguments.length) {
case 1:
return bin.substr(idx, bin.length - idx - 1);
case 2:
return bin.substr(idx, length);
case 3:
putstr(segment, idx, length);
break;
default: return bin;
}
},
BYTE: function(idx) {
return read(idx, 1);
},
SHORT: function(idx) {
return read(idx, 2);
},
LONG: function(idx, num) {
if (num === undefined) {
return read(idx, 4);
} else {
write(idx, num, 4);
}
},
SLONG: function(idx) { // 2's complement notation
var num = read(idx, 4);
return (num > 2147483647 ? num - 4294967296 : num);
},
STRING: function(idx, size) {
var str = '';
for (size += idx; idx < size; idx++) {
str += String.fromCharCode(read(idx, 1));
}
return str;
}
};
};
});
// Included from: src/javascript/runtime/html5/image/JPEGHeaders.js
/**
* JPEGHeaders.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/image/JPEGHeaders
@private
*/
define("moxie/runtime/html5/image/JPEGHeaders", [
"moxie/runtime/html5/utils/BinaryReader"
], function(BinaryReader) {
return function JPEGHeaders(data) {
var headers = [], read, idx, marker, length = 0;
read = new BinaryReader();
read.init(data);
// Check if data is jpeg
if (read.SHORT(0) !== 0xFFD8) {
return;
}
idx = 2;
while (idx <= data.length) {
marker = read.SHORT(idx);
// omit RST (restart) markers
if (marker >= 0xFFD0 && marker <= 0xFFD7) {
idx += 2;
continue;
}
// no headers allowed after SOS marker
if (marker === 0xFFDA || marker === 0xFFD9) {
break;
}
length = read.SHORT(idx + 2) + 2;
// APPn marker detected
if (marker >= 0xFFE1 && marker <= 0xFFEF) {
headers.push({
hex: marker,
name: 'APP' + (marker & 0x000F),
start: idx,
length: length,
segment: read.SEGMENT(idx, length)
});
}
idx += length;
}
read.init(null); // free memory
return {
headers: headers,
restore: function(data) {
var max, i;
read.init(data);
idx = read.SHORT(2) == 0xFFE0 ? 4 + read.SHORT(4) : 2;
for (i = 0, max = headers.length; i < max; i++) {
read.SEGMENT(idx, 0, headers[i].segment);
idx += headers[i].length;
}
data = read.SEGMENT();
read.init(null);
return data;
},
strip: function(data) {
var headers, jpegHeaders, i;
jpegHeaders = new JPEGHeaders(data);
headers = jpegHeaders.headers;
jpegHeaders.purge();
read.init(data);
i = headers.length;
while (i--) {
read.SEGMENT(headers[i].start, headers[i].length, '');
}
data = read.SEGMENT();
read.init(null);
return data;
},
get: function(name) {
var array = [];
for (var i = 0, max = headers.length; i < max; i++) {
if (headers[i].name === name.toUpperCase()) {
array.push(headers[i].segment);
}
}
return array;
},
set: function(name, segment) {
var array = [], i, ii, max;
if (typeof(segment) === 'string') {
array.push(segment);
} else {
array = segment;
}
for (i = ii = 0, max = headers.length; i < max; i++) {
if (headers[i].name === name.toUpperCase()) {
headers[i].segment = array[ii];
headers[i].length = array[ii].length;
ii++;
}
if (ii >= array.length) {
break;
}
}
},
purge: function() {
headers = [];
read.init(null);
read = null;
}
};
};
});
// Included from: src/javascript/runtime/html5/image/ExifParser.js
/**
* ExifParser.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/image/ExifParser
@private
*/
define("moxie/runtime/html5/image/ExifParser", [
"moxie/core/utils/Basic",
"moxie/runtime/html5/utils/BinaryReader"
], function(Basic, BinaryReader) {
return function ExifParser() {
// Private ExifParser fields
var data, tags, Tiff, offsets = {}, tagDescs;
data = new BinaryReader();
tags = {
tiff : {
/*
The image orientation viewed in terms of rows and columns.
1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
*/
0x0112: 'Orientation',
0x010E: 'ImageDescription',
0x010F: 'Make',
0x0110: 'Model',
0x0131: 'Software',
0x8769: 'ExifIFDPointer',
0x8825: 'GPSInfoIFDPointer'
},
exif : {
0x9000: 'ExifVersion',
0xA001: 'ColorSpace',
0xA002: 'PixelXDimension',
0xA003: 'PixelYDimension',
0x9003: 'DateTimeOriginal',
0x829A: 'ExposureTime',
0x829D: 'FNumber',
0x8827: 'ISOSpeedRatings',
0x9201: 'ShutterSpeedValue',
0x9202: 'ApertureValue' ,
0x9207: 'MeteringMode',
0x9208: 'LightSource',
0x9209: 'Flash',
0x920A: 'FocalLength',
0xA402: 'ExposureMode',
0xA403: 'WhiteBalance',
0xA406: 'SceneCaptureType',
0xA404: 'DigitalZoomRatio',
0xA408: 'Contrast',
0xA409: 'Saturation',
0xA40A: 'Sharpness'
},
gps : {
0x0000: 'GPSVersionID',
0x0001: 'GPSLatitudeRef',
0x0002: 'GPSLatitude',
0x0003: 'GPSLongitudeRef',
0x0004: 'GPSLongitude'
}
};
tagDescs = {
'ColorSpace': {
1: 'sRGB',
0: 'Uncalibrated'
},
'MeteringMode': {
0: 'Unknown',
1: 'Average',
2: 'CenterWeightedAverage',
3: 'Spot',
4: 'MultiSpot',
5: 'Pattern',
6: 'Partial',
255: 'Other'
},
'LightSource': {
1: 'Daylight',
2: 'Fliorescent',
3: 'Tungsten',
4: 'Flash',
9: 'Fine weather',
10: 'Cloudy weather',
11: 'Shade',
12: 'Daylight fluorescent (D 5700 - 7100K)',
13: 'Day white fluorescent (N 4600 -5400K)',
14: 'Cool white fluorescent (W 3900 - 4500K)',
15: 'White fluorescent (WW 3200 - 3700K)',
17: 'Standard light A',
18: 'Standard light B',
19: 'Standard light C',
20: 'D55',
21: 'D65',
22: 'D75',
23: 'D50',
24: 'ISO studio tungsten',
255: 'Other'
},
'Flash': {
0x0000: 'Flash did not fire.',
0x0001: 'Flash fired.',
0x0005: 'Strobe return light not detected.',
0x0007: 'Strobe return light detected.',
0x0009: 'Flash fired, compulsory flash mode',
0x000D: 'Flash fired, compulsory flash mode, return light not detected',
0x000F: 'Flash fired, compulsory flash mode, return light detected',
0x0010: 'Flash did not fire, compulsory flash mode',
0x0018: 'Flash did not fire, auto mode',
0x0019: 'Flash fired, auto mode',
0x001D: 'Flash fired, auto mode, return light not detected',
0x001F: 'Flash fired, auto mode, return light detected',
0x0020: 'No flash function',
0x0041: 'Flash fired, red-eye reduction mode',
0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
0x0047: 'Flash fired, red-eye reduction mode, return light detected',
0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
0x004D: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
0x004F: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
0x0059: 'Flash fired, auto mode, red-eye reduction mode',
0x005D: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
0x005F: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
},
'ExposureMode': {
0: 'Auto exposure',
1: 'Manual exposure',
2: 'Auto bracket'
},
'WhiteBalance': {
0: 'Auto white balance',
1: 'Manual white balance'
},
'SceneCaptureType': {
0: 'Standard',
1: 'Landscape',
2: 'Portrait',
3: 'Night scene'
},
'Contrast': {
0: 'Normal',
1: 'Soft',
2: 'Hard'
},
'Saturation': {
0: 'Normal',
1: 'Low saturation',
2: 'High saturation'
},
'Sharpness': {
0: 'Normal',
1: 'Soft',
2: 'Hard'
},
// GPS related
'GPSLatitudeRef': {
N: 'North latitude',
S: 'South latitude'
},
'GPSLongitudeRef': {
E: 'East longitude',
W: 'West longitude'
}
};
function extractTags(IFD_offset, tags2extract) {
var length = data.SHORT(IFD_offset), i, ii,
tag, type, count, tagOffset, offset, value, values = [], hash = {};
for (i = 0; i < length; i++) {
// Set binary reader pointer to beginning of the next tag
offset = tagOffset = IFD_offset + 12 * i + 2;
tag = tags2extract[data.SHORT(offset)];
if (tag === undefined) {
continue; // Not the tag we requested
}
type = data.SHORT(offset+=2);
count = data.LONG(offset+=2);
offset += 4;
values = [];
switch (type) {
case 1: // BYTE
case 7: // UNDEFINED
if (count > 4) {
offset = data.LONG(offset) + offsets.tiffHeader;
}
for (ii = 0; ii < count; ii++) {
values[ii] = data.BYTE(offset + ii);
}
break;
case 2: // STRING
if (count > 4) {
offset = data.LONG(offset) + offsets.tiffHeader;
}
hash[tag] = data.STRING(offset, count - 1);
continue;
case 3: // SHORT
if (count > 2) {
offset = data.LONG(offset) + offsets.tiffHeader;
}
for (ii = 0; ii < count; ii++) {
values[ii] = data.SHORT(offset + ii*2);
}
break;
case 4: // LONG
if (count > 1) {
offset = data.LONG(offset) + offsets.tiffHeader;
}
for (ii = 0; ii < count; ii++) {
values[ii] = data.LONG(offset + ii*4);
}
break;
case 5: // RATIONAL
offset = data.LONG(offset) + offsets.tiffHeader;
for (ii = 0; ii < count; ii++) {
values[ii] = data.LONG(offset + ii*4) / data.LONG(offset + ii*4 + 4);
}
break;
case 9: // SLONG
offset = data.LONG(offset) + offsets.tiffHeader;
for (ii = 0; ii < count; ii++) {
values[ii] = data.SLONG(offset + ii*4);
}
break;
case 10: // SRATIONAL
offset = data.LONG(offset) + offsets.tiffHeader;
for (ii = 0; ii < count; ii++) {
values[ii] = data.SLONG(offset + ii*4) / data.SLONG(offset + ii*4 + 4);
}
break;
default:
continue;
}
value = (count == 1 ? values[0] : values);
if (tagDescs.hasOwnProperty(tag) && typeof value != 'object') {
hash[tag] = tagDescs[tag][value];
} else {
hash[tag] = value;
}
}
return hash;
}
function getIFDOffsets() {
var idx = offsets.tiffHeader;
// Set read order of multi-byte data
data.II(data.SHORT(idx) == 0x4949);
// Check if always present bytes are indeed present
if (data.SHORT(idx+=2) !== 0x002A) {
return false;
}
offsets.IFD0 = offsets.tiffHeader + data.LONG(idx += 2);
Tiff = extractTags(offsets.IFD0, tags.tiff);
if ('ExifIFDPointer' in Tiff) {
offsets.exifIFD = offsets.tiffHeader + Tiff.ExifIFDPointer;
delete Tiff.ExifIFDPointer;
}
if ('GPSInfoIFDPointer' in Tiff) {
offsets.gpsIFD = offsets.tiffHeader + Tiff.GPSInfoIFDPointer;
delete Tiff.GPSInfoIFDPointer;
}
return true;
}
// At the moment only setting of simple (LONG) values, that do not require offset recalculation, is supported
function setTag(ifd, tag, value) {
var offset, length, tagOffset, valueOffset = 0;
// If tag name passed translate into hex key
if (typeof(tag) === 'string') {
var tmpTags = tags[ifd.toLowerCase()];
for (var hex in tmpTags) {
if (tmpTags[hex] === tag) {
tag = hex;
break;
}
}
}
offset = offsets[ifd.toLowerCase() + 'IFD'];
length = data.SHORT(offset);
for (var i = 0; i < length; i++) {
tagOffset = offset + 12 * i + 2;
if (data.SHORT(tagOffset) == tag) {
valueOffset = tagOffset + 8;
break;
}
}
if (!valueOffset) {
return false;
}
data.LONG(valueOffset, value);
return true;
}
// Public functions
return {
init: function(segment) {
// Reset internal data
offsets = {
tiffHeader: 10
};
if (segment === undefined || !segment.length) {
return false;
}
data.init(segment);
// Check if that's APP1 and that it has EXIF
if (data.SHORT(0) === 0xFFE1 && data.STRING(4, 5).toUpperCase() === "EXIF\0") {
return getIFDOffsets();
}
return false;
},
TIFF: function() {
return Tiff;
},
EXIF: function() {
var Exif;
// Populate EXIF hash
Exif = extractTags(offsets.exifIFD, tags.exif);
// Fix formatting of some tags
if (Exif.ExifVersion && Basic.typeOf(Exif.ExifVersion) === 'array') {
for (var i = 0, exifVersion = ''; i < Exif.ExifVersion.length; i++) {
exifVersion += String.fromCharCode(Exif.ExifVersion[i]);
}
Exif.ExifVersion = exifVersion;
}
return Exif;
},
GPS: function() {
var GPS;
GPS = extractTags(offsets.gpsIFD, tags.gps);
// iOS devices (and probably some others) do not put in GPSVersionID tag (why?..)
if (GPS.GPSVersionID && Basic.typeOf(GPS.GPSVersionID) === 'array') {
GPS.GPSVersionID = GPS.GPSVersionID.join('.');
}
return GPS;
},
setExif: function(tag, value) {
// Right now only setting of width/height is possible
if (tag !== 'PixelXDimension' && tag !== 'PixelYDimension') {return false;}
return setTag('exif', tag, value);
},
getBinary: function() {
return data.SEGMENT();
},
purge: function() {
data.init(null);
data = Tiff = null;
offsets = {};
}
};
};
});
// Included from: src/javascript/runtime/html5/image/JPEG.js
/**
* JPEG.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/image/JPEG
@private
*/
define("moxie/runtime/html5/image/JPEG", [
"moxie/core/utils/Basic",
"moxie/core/Exceptions",
"moxie/runtime/html5/image/JPEGHeaders",
"moxie/runtime/html5/utils/BinaryReader",
"moxie/runtime/html5/image/ExifParser"
], function(Basic, x, JPEGHeaders, BinaryReader, ExifParser) {
function JPEG(binstr) {
var _binstr, _br, _hm, _ep, _info, hasExif;
function _getDimensions() {
var idx = 0, marker, length;
// examine all through the end, since some images might have very large APP segments
while (idx <= _binstr.length) {
marker = _br.SHORT(idx += 2);
if (marker >= 0xFFC0 && marker <= 0xFFC3) { // SOFn
idx += 5; // marker (2 bytes) + length (2 bytes) + Sample precision (1 byte)
return {
height: _br.SHORT(idx),
width: _br.SHORT(idx += 2)
};
}
length = _br.SHORT(idx += 2);
idx += length - 2;
}
return null;
}
_binstr = binstr;
_br = new BinaryReader();
_br.init(_binstr);
// check if it is jpeg
if (_br.SHORT(0) !== 0xFFD8) {
throw new x.ImageError(x.ImageError.WRONG_FORMAT);
}
// backup headers
_hm = new JPEGHeaders(binstr);
// extract exif info
_ep = new ExifParser();
hasExif = !!_ep.init(_hm.get('app1')[0]);
// get dimensions
_info = _getDimensions.call(this);
Basic.extend(this, {
type: 'image/jpeg',
size: _binstr.length,
width: _info && _info.width || 0,
height: _info && _info.height || 0,
setExif: function(tag, value) {
if (!hasExif) {
return false; // or throw an exception
}
if (Basic.typeOf(tag) === 'object') {
Basic.each(tag, function(value, tag) {
_ep.setExif(tag, value);
});
} else {
_ep.setExif(tag, value);
}
// update internal headers
_hm.set('app1', _ep.getBinary());
},
writeHeaders: function() {
if (!arguments.length) {
// if no arguments passed, update headers internally
return (_binstr = _hm.restore(_binstr));
}
return _hm.restore(arguments[0]);
},
stripHeaders: function(binstr) {
return _hm.strip(binstr);
},
purge: function() {
_purge.call(this);
}
});
if (hasExif) {
this.meta = {
tiff: _ep.TIFF(),
exif: _ep.EXIF(),
gps: _ep.GPS()
};
}
function _purge() {
if (!_ep || !_hm || !_br) {
return; // ignore any repeating purge requests
}
_ep.purge();
_hm.purge();
_br.init(null);
_binstr = _info = _hm = _ep = _br = null;
}
}
return JPEG;
});
// Included from: src/javascript/runtime/html5/image/PNG.js
/**
* PNG.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/image/PNG
@private
*/
define("moxie/runtime/html5/image/PNG", [
"moxie/core/Exceptions",
"moxie/core/utils/Basic",
"moxie/runtime/html5/utils/BinaryReader"
], function(x, Basic, BinaryReader) {
function PNG(binstr) {
var _binstr, _br, _hm, _ep, _info;
_binstr = binstr;
_br = new BinaryReader();
_br.init(_binstr);
// check if it's png
(function() {
var idx = 0, i = 0
, signature = [0x8950, 0x4E47, 0x0D0A, 0x1A0A]
;
for (i = 0; i < signature.length; i++, idx += 2) {
if (signature[i] != _br.SHORT(idx)) {
throw new x.ImageError(x.ImageError.WRONG_FORMAT);
}
}
}());
function _getDimensions() {
var chunk, idx;
chunk = _getChunkAt.call(this, 8);
if (chunk.type == 'IHDR') {
idx = chunk.start;
return {
width: _br.LONG(idx),
height: _br.LONG(idx += 4)
};
}
return null;
}
function _purge() {
if (!_br) {
return; // ignore any repeating purge requests
}
_br.init(null);
_binstr = _info = _hm = _ep = _br = null;
}
_info = _getDimensions.call(this);
Basic.extend(this, {
type: 'image/png',
size: _binstr.length,
width: _info.width,
height: _info.height,
purge: function() {
_purge.call(this);
}
});
// for PNG we can safely trigger purge automatically, as we do not keep any data for later
_purge.call(this);
function _getChunkAt(idx) {
var length, type, start, CRC;
length = _br.LONG(idx);
type = _br.STRING(idx += 4, 4);
start = idx += 4;
CRC = _br.LONG(idx + length);
return {
length: length,
type: type,
start: start,
CRC: CRC
};
}
}
return PNG;
});
// Included from: src/javascript/runtime/html5/image/ImageInfo.js
/**
* ImageInfo.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/image/ImageInfo
@private
*/
define("moxie/runtime/html5/image/ImageInfo", [
"moxie/core/utils/Basic",
"moxie/core/Exceptions",
"moxie/runtime/html5/image/JPEG",
"moxie/runtime/html5/image/PNG"
], function(Basic, x, JPEG, PNG) {
/**
Optional image investigation tool for HTML5 runtime. Provides the following features:
- ability to distinguish image type (JPEG or PNG) by signature
- ability to extract image width/height directly from it's internals, without preloading in memory (fast)
- ability to extract APP headers from JPEGs (Exif, GPS, etc)
- ability to replace width/height tags in extracted JPEG headers
- ability to restore APP headers, that were for example stripped during image manipulation
@class ImageInfo
@constructor
@param {String} binstr Image source as binary string
*/
return function(binstr) {
var _cs = [JPEG, PNG], _img;
// figure out the format, throw: ImageError.WRONG_FORMAT if not supported
_img = (function() {
for (var i = 0; i < _cs.length; i++) {
try {
return new _cs[i](binstr);
} catch (ex) {
// console.info(ex);
}
}
throw new x.ImageError(x.ImageError.WRONG_FORMAT);
}());
Basic.extend(this, {
/**
Image Mime Type extracted from it's depths
@property type
@type {String}
@default ''
*/
type: '',
/**
Image size in bytes
@property size
@type {Number}
@default 0
*/
size: 0,
/**
Image width extracted from image source
@property width
@type {Number}
@default 0
*/
width: 0,
/**
Image height extracted from image source
@property height
@type {Number}
@default 0
*/
height: 0,
/**
Sets Exif tag. Currently applicable only for width and height tags. Obviously works only with JPEGs.
@method setExif
@param {String} tag Tag to set
@param {Mixed} value Value to assign to the tag
*/
setExif: function() {},
/**
Restores headers to the source.
@method writeHeaders
@param {String} data Image source as binary string
@return {String} Updated binary string
*/
writeHeaders: function(data) {
return data;
},
/**
Strip all headers from the source.
@method stripHeaders
@param {String} data Image source as binary string
@return {String} Updated binary string
*/
stripHeaders: function(data) {
return data;
},
/**
Dispose resources.
@method purge
*/
purge: function() {}
});
Basic.extend(this, _img);
this.purge = function() {
_img.purge();
_img = null;
};
};
});
// Included from: src/javascript/runtime/html5/image/MegaPixel.js
/**
(The MIT License)
Copyright (c) 2012 Shinichi Tomita ;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Mega pixel image rendering library for iOS6 Safari
*
* Fixes iOS6 Safari's image file rendering issue for large size image (over mega-pixel),
* which causes unexpected subsampling when drawing it in canvas.
* By using this library, you can safely render the image with proper stretching.
*
* Copyright (c) 2012 Shinichi Tomita
* Released under the MIT license
*/
/**
@class moxie/runtime/html5/image/MegaPixel
@private
*/
define("moxie/runtime/html5/image/MegaPixel", [], function() {
/**
* Rendering image element (with resizing) into the canvas element
*/
function renderImageToCanvas(img, canvas, options) {
var iw = img.naturalWidth, ih = img.naturalHeight;
var width = options.width, height = options.height;
var x = options.x || 0, y = options.y || 0;
var ctx = canvas.getContext('2d');
if (detectSubsampling(img)) {
iw /= 2;
ih /= 2;
}
var d = 1024; // size of tiling canvas
var tmpCanvas = document.createElement('canvas');
tmpCanvas.width = tmpCanvas.height = d;
var tmpCtx = tmpCanvas.getContext('2d');
var vertSquashRatio = detectVerticalSquash(img, iw, ih);
var sy = 0;
while (sy < ih) {
var sh = sy + d > ih ? ih - sy : d;
var sx = 0;
while (sx < iw) {
var sw = sx + d > iw ? iw - sx : d;
tmpCtx.clearRect(0, 0, d, d);
tmpCtx.drawImage(img, -sx, -sy);
var dx = (sx * width / iw + x) << 0;
var dw = Math.ceil(sw * width / iw);
var dy = (sy * height / ih / vertSquashRatio + y) << 0;
var dh = Math.ceil(sh * height / ih / vertSquashRatio);
ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh);
sx += d;
}
sy += d;
}
tmpCanvas = tmpCtx = null;
}
/**
* Detect subsampling in loaded image.
* In iOS, larger images than 2M pixels may be subsampled in rendering.
*/
function detectSubsampling(img) {
var iw = img.naturalWidth, ih = img.naturalHeight;
if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, -iw + 1, 0);
// subsampled image becomes half smaller in rendering size.
// check alpha channel value to confirm image is covering edge pixel or not.
// if alpha value is 0 image is not covering, hence subsampled.
return ctx.getImageData(0, 0, 1, 1).data[3] === 0;
} else {
return false;
}
}
/**
* Detecting vertical squash in loaded image.
* Fixes a bug which squash image vertically while drawing into canvas for some images.
*/
function detectVerticalSquash(img, iw, ih) {
var canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = ih;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
var data = ctx.getImageData(0, 0, 1, ih).data;
// search image edge pixel position in case it is squashed vertically.
var sy = 0;
var ey = ih;
var py = ih;
while (py > sy) {
var alpha = data[(py - 1) * 4 + 3];
if (alpha === 0) {
ey = py;
} else {
sy = py;
}
py = (ey + sy) >> 1;
}
canvas = null;
var ratio = (py / ih);
return (ratio === 0) ? 1 : ratio;
}
return {
isSubsampled: detectSubsampling,
renderTo: renderImageToCanvas
};
});
// Included from: src/javascript/runtime/html5/image/Image.js
/**
* Image.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html5/image/Image
@private
*/
define("moxie/runtime/html5/image/Image", [
"moxie/runtime/html5/Runtime",
"moxie/core/utils/Basic",
"moxie/core/Exceptions",
"moxie/core/utils/Encode",
"moxie/file/File",
"moxie/runtime/html5/image/ImageInfo",
"moxie/runtime/html5/image/MegaPixel",
"moxie/core/utils/Mime",
"moxie/core/utils/Env"
], function(extensions, Basic, x, Encode, File, ImageInfo, MegaPixel, Mime, Env) {
function HTML5Image() {
var me = this
, _img, _imgInfo, _canvas, _binStr, _blob
, _modified = false // is set true whenever image is modified
, _preserveHeaders = true
;
Basic.extend(this, {
loadFromBlob: function(blob) {
var comp = this, I = comp.getRuntime()
, asBinary = arguments.length > 1 ? arguments[1] : true
;
if (!I.can('access_binary')) {
throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR);
}
_blob = blob;
if (blob.isDetached()) {
_binStr = blob.getSource();
_preload.call(this, _binStr);
return;
} else {
_readAsDataUrl.call(this, blob.getSource(), function(dataUrl) {
if (asBinary) {
_binStr = _toBinary(dataUrl);
}
_preload.call(comp, dataUrl);
});
}
},
loadFromImage: function(img, exact) {
this.meta = img.meta;
_blob = new File(null, {
name: img.name,
size: img.size,
type: img.type
});
_preload.call(this, exact ? (_binStr = img.getAsBinaryString()) : img.getAsDataURL());
},
getInfo: function() {
var I = this.getRuntime(), info;
if (!_imgInfo && _binStr && I.can('access_image_binary')) {
_imgInfo = new ImageInfo(_binStr);
}
info = {
width: _getImg().width || 0,
height: _getImg().height || 0,
type: _blob.type || Mime.getFileMime(_blob.name),
size: _binStr && _binStr.length || _blob.size || 0,
name: _blob.name || '',
meta: _imgInfo && _imgInfo.meta || this.meta || {}
};
return info;
},
downsize: function() {
_downsize.apply(this, arguments);
},
getAsCanvas: function() {
if (_canvas) {
_canvas.id = this.uid + '_canvas';
}
return _canvas;
},
getAsBlob: function(type, quality) {
if (type !== this.type) {
// if different mime type requested prepare image for conversion
_downsize.call(this, this.width, this.height, false);
}
return new File(null, {
name: _blob.name || '',
type: type,
data: me.getAsBinaryString.call(this, type, quality)
});
},
getAsDataURL: function(type) {
var quality = arguments[1] || 90;
// if image has not been modified, return the source right away
if (!_modified) {
return _img.src;
}
if ('image/jpeg' !== type) {
return _canvas.toDataURL('image/png');
} else {
try {
// older Geckos used to result in an exception on quality argument
return _canvas.toDataURL('image/jpeg', quality/100);
} catch (ex) {
return _canvas.toDataURL('image/jpeg');
}
}
},
getAsBinaryString: function(type, quality) {
// if image has not been modified, return the source right away
if (!_modified) {
// if image was not loaded from binary string
if (!_binStr) {
_binStr = _toBinary(me.getAsDataURL(type, quality));
}
return _binStr;
}
if ('image/jpeg' !== type) {
_binStr = _toBinary(me.getAsDataURL(type, quality));
} else {
var dataUrl;
// if jpeg
if (!quality) {
quality = 90;
}
try {
// older Geckos used to result in an exception on quality argument
dataUrl = _canvas.toDataURL('image/jpeg', quality/100);
} catch (ex) {
dataUrl = _canvas.toDataURL('image/jpeg');
}
_binStr = _toBinary(dataUrl);
if (_imgInfo) {
_binStr = _imgInfo.stripHeaders(_binStr);
if (_preserveHeaders) {
// update dimensions info in exif
if (_imgInfo.meta && _imgInfo.meta.exif) {
_imgInfo.setExif({
PixelXDimension: this.width,
PixelYDimension: this.height
});
}
// re-inject the headers
_binStr = _imgInfo.writeHeaders(_binStr);
}
// will be re-created from fresh on next getInfo call
_imgInfo.purge();
_imgInfo = null;
}
}
_modified = false;
return _binStr;
},
destroy: function() {
me = null;
_purge.call(this);
this.getRuntime().getShim().removeInstance(this.uid);
}
});
function _getImg() {
if (!_canvas && !_img) {
throw new x.ImageError(x.DOMException.INVALID_STATE_ERR);
}
return _canvas || _img;
}
function _toBinary(str) {
return Encode.atob(str.substring(str.indexOf('base64,') + 7));
}
function _toDataUrl(str, type) {
return 'data:' + (type || '') + ';base64,' + Encode.btoa(str);
}
function _preload(str) {
var comp = this;
_img = new Image();
_img.onerror = function() {
_purge.call(this);
comp.trigger('error', x.ImageError.WRONG_FORMAT);
};
_img.onload = function() {
comp.trigger('load');
};
_img.src = /^data:[^;]*;base64,/.test(str) ? str : _toDataUrl(str, _blob.type);
}
function _readAsDataUrl(file, callback) {
var comp = this, fr;
// use FileReader if it's available
if (window.FileReader) {
fr = new FileReader();
fr.onload = function() {
callback(this.result);
};
fr.onerror = function() {
comp.trigger('error', x.ImageError.WRONG_FORMAT);
};
fr.readAsDataURL(file);
} else {
return callback(file.getAsDataURL());
}
}
function _downsize(width, height, crop, preserveHeaders) {
var self = this
, scale
, mathFn
, x = 0
, y = 0
, img
, destWidth
, destHeight
, orientation
;
_preserveHeaders = preserveHeaders; // we will need to check this on export (see getAsBinaryString())
// take into account orientation tag
orientation = (this.meta && this.meta.tiff && this.meta.tiff.Orientation) || 1;
if (Basic.inArray(orientation, [5,6,7,8]) !== -1) { // values that require 90 degree rotation
// swap dimensions
var tmp = width;
width = height;
height = tmp;
}
img = _getImg();
// unify dimensions
if (!crop) {
scale = Math.min(width/img.width, height/img.height);
} else {
// one of the dimensions may exceed the actual image dimensions - we need to take the smallest value
width = Math.min(width, img.width);
height = Math.min(height, img.height);
scale = Math.max(width/img.width, height/img.height);
}
// we only downsize here
if (scale > 1 && !crop && preserveHeaders) {
this.trigger('Resize');
return;
}
// prepare canvas if necessary
if (!_canvas) {
_canvas = document.createElement("canvas");
}
// calculate dimensions of proportionally resized image
destWidth = Math.round(img.width * scale);
destHeight = Math.round(img.height * scale);
// scale image and canvas
if (crop) {
_canvas.width = width;
_canvas.height = height;
// if dimensions of the resulting image still larger than canvas, center it
if (destWidth > width) {
x = Math.round((destWidth - width) / 2);
}
if (destHeight > height) {
y = Math.round((destHeight - height) / 2);
}
} else {
_canvas.width = destWidth;
_canvas.height = destHeight;
}
// rotate if required, according to orientation tag
if (!_preserveHeaders) {
_rotateToOrientaion(_canvas.width, _canvas.height, orientation);
}
_drawToCanvas.call(this, img, _canvas, -x, -y, destWidth, destHeight);
this.width = _canvas.width;
this.height = _canvas.height;
_modified = true;
self.trigger('Resize');
}
function _drawToCanvas(img, canvas, x, y, w, h) {
if (Env.OS === 'iOS') {
// avoid squish bug in iOS6
MegaPixel.renderTo(img, canvas, { width: w, height: h, x: x, y: y });
} else {
var ctx = canvas.getContext('2d');
ctx.drawImage(img, x, y, w, h);
}
}
/**
* Transform canvas coordination according to specified frame size and orientation
* Orientation value is from EXIF tag
* @author Shinichi Tomita
*/
function _rotateToOrientaion(width, height, orientation) {
switch (orientation) {
case 5:
case 6:
case 7:
case 8:
_canvas.width = height;
_canvas.height = width;
break;
default:
_canvas.width = width;
_canvas.height = height;
}
/**
1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
*/
var ctx = _canvas.getContext('2d');
switch (orientation) {
case 2:
// horizontal flip
ctx.translate(width, 0);
ctx.scale(-1, 1);
break;
case 3:
// 180 rotate left
ctx.translate(width, height);
ctx.rotate(Math.PI);
break;
case 4:
// vertical flip
ctx.translate(0, height);
ctx.scale(1, -1);
break;
case 5:
// vertical flip + 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.scale(1, -1);
break;
case 6:
// 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.translate(0, -height);
break;
case 7:
// horizontal flip + 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.translate(width, -height);
ctx.scale(-1, 1);
break;
case 8:
// 90 rotate left
ctx.rotate(-0.5 * Math.PI);
ctx.translate(-width, 0);
break;
}
}
function _purge() {
if (_imgInfo) {
_imgInfo.purge();
_imgInfo = null;
}
_binStr = _img = _canvas = _blob = null;
_modified = false;
}
}
return (extensions.Image = HTML5Image);
});
// Included from: src/javascript/runtime/flash/Runtime.js
/**
* Runtime.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/*global ActiveXObject:true */
/**
Defines constructor for Flash runtime.
@class moxie/runtime/flash/Runtime
@private
*/
define("moxie/runtime/flash/Runtime", [
"moxie/core/utils/Basic",
"moxie/core/utils/Env",
"moxie/core/utils/Dom",
"moxie/core/Exceptions",
"moxie/runtime/Runtime"
], function(Basic, Env, Dom, x, Runtime) {
var type = 'flash', extensions = {};
/**
Get the version of the Flash Player
@method getShimVersion
@private
@return {Number} Flash Player version
*/
function getShimVersion() {
var version;
try {
version = navigator.plugins['Shockwave Flash'];
version = version.description;
} catch (e1) {
try {
version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
} catch (e2) {
version = '0.0';
}
}
version = version.match(/\d+/g);
return parseFloat(version[0] + '.' + version[1]);
}
/**
Constructor for the Flash Runtime
@class FlashRuntime
@extends Runtime
*/
function FlashRuntime(options) {
var I = this, initTimer;
options = Basic.extend({ swf_url: Env.swf_url }, options);
Runtime.call(this, options, type, {
access_binary: function(value) {
return value && I.mode === 'browser';
},
access_image_binary: function(value) {
return value && I.mode === 'browser';
},
display_media: Runtime.capTrue,
do_cors: Runtime.capTrue,
drag_and_drop: false,
report_upload_progress: function() {
return I.mode === 'client';
},
resize_image: Runtime.capTrue,
return_response_headers: false,
return_response_type: function(responseType) {
if (responseType === 'json' && !!window.JSON) {
return true;
}
return !Basic.arrayDiff(responseType, ['', 'text', 'document']) || I.mode === 'browser';
},
return_status_code: function(code) {
return I.mode === 'browser' || !Basic.arrayDiff(code, [200, 404]);
},
select_file: Runtime.capTrue,
select_multiple: Runtime.capTrue,
send_binary_string: function(value) {
return value && I.mode === 'browser';
},
send_browser_cookies: function(value) {
return value && I.mode === 'browser';
},
send_custom_headers: function(value) {
return value && I.mode === 'browser';
},
send_multipart: Runtime.capTrue,
slice_blob: function(value) {
return value && I.mode === 'browser';
},
stream_upload: function(value) {
return value && I.mode === 'browser';
},
summon_file_dialog: false,
upload_filesize: function(size) {
return Basic.parseSizeStr(size) <= 2097152 || I.mode === 'client';
},
use_http_method: function(methods) {
return !Basic.arrayDiff(methods, ['GET', 'POST']);
}
}, {
// capabilities that require specific mode
access_binary: function(value) {
return value ? 'browser' : 'client';
},
access_image_binary: function(value) {
return value ? 'browser' : 'client';
},
report_upload_progress: function(value) {
return value ? 'browser' : 'client';
},
return_response_type: function(responseType) {
return Basic.arrayDiff(responseType, ['', 'text', 'json', 'document']) ? 'browser' : ['client', 'browser'];
},
return_status_code: function(code) {
return Basic.arrayDiff(code, [200, 404]) ? 'browser' : ['client', 'browser'];
},
send_binary_string: function(value) {
return value ? 'browser' : 'client';
},
send_browser_cookies: function(value) {
return value ? 'browser' : 'client';
},
send_custom_headers: function(value) {
return value ? 'browser' : 'client';
},
stream_upload: function(value) {
return value ? 'client' : 'browser';
},
upload_filesize: function(size) {
return Basic.parseSizeStr(size) >= 2097152 ? 'client' : 'browser';
}
}, 'client');
// minimal requirement for Flash Player version
if (getShimVersion() < 10) {
this.mode = false; // with falsy mode, runtime won't operable, no matter what the mode was before
}
Basic.extend(this, {
getShim: function() {
return Dom.get(this.uid);
},
shimExec: function(component, action) {
var args = [].slice.call(arguments, 2);
return I.getShim().exec(this.uid, component, action, args);
},
init: function() {
var html, el, container;
container = this.getShimContainer();
// if not the minimal height, shims are not initialized in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
Basic.extend(container.style, {
position: 'absolute',
top: '-8px',
left: '-8px',
width: '9px',
height: '9px',
overflow: 'hidden'
});
// insert flash object
html = '';
if (Env.browser === 'IE') {
el = document.createElement('div');
container.appendChild(el);
el.outerHTML = html;
el = container = null; // just in case
} else {
container.innerHTML = html;
}
// Init is dispatched by the shim
initTimer = setTimeout(function() {
if (I && !I.initialized) { // runtime might be already destroyed by this moment
I.trigger("Error", new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR));
}
}, 5000);
},
destroy: (function(destroy) { // extend default destroy method
return function() {
destroy.call(I);
clearTimeout(initTimer); // initialization check might be still onwait
options = initTimer = destroy = I = null;
};
}(this.destroy))
}, extensions);
}
Runtime.addConstructor(type, FlashRuntime);
return extensions;
});
// Included from: src/javascript/runtime/flash/file/Blob.js
/**
* Blob.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/flash/file/Blob
@private
*/
define("moxie/runtime/flash/file/Blob", [
"moxie/runtime/flash/Runtime",
"moxie/file/Blob"
], function(extensions, Blob) {
var FlashBlob = {
slice: function(blob, start, end, type) {
var self = this.getRuntime();
if (start < 0) {
start = Math.max(blob.size + start, 0);
} else if (start > 0) {
start = Math.min(start, blob.size);
}
if (end < 0) {
end = Math.max(blob.size + end, 0);
} else if (end > 0) {
end = Math.min(end, blob.size);
}
blob = self.shimExec.call(this, 'Blob', 'slice', start, end, type || '');
if (blob) {
blob = new Blob(self.uid, blob);
}
return blob;
}
};
return (extensions.Blob = FlashBlob);
});
// Included from: src/javascript/runtime/flash/file/FileInput.js
/**
* FileInput.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/flash/file/FileInput
@private
*/
define("moxie/runtime/flash/file/FileInput", [
"moxie/runtime/flash/Runtime"
], function(extensions) {
var FileInput = {
init: function(options) {
this.getRuntime().shimExec.call(this, 'FileInput', 'init', {
name: options.name,
accept: options.accept,
multiple: options.multiple
});
this.trigger('ready');
}
};
return (extensions.FileInput = FileInput);
});
// Included from: src/javascript/runtime/flash/file/FileReader.js
/**
* FileReader.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/flash/file/FileReader
@private
*/
define("moxie/runtime/flash/file/FileReader", [
"moxie/runtime/flash/Runtime",
"moxie/core/utils/Encode"
], function(extensions, Encode) {
var _result = '';
function _formatData(data, op) {
switch (op) {
case 'readAsText':
return Encode.atob(data, 'utf8');
case 'readAsBinaryString':
return Encode.atob(data);
case 'readAsDataURL':
return data;
}
return null;
}
var FileReader = {
read: function(op, blob) {
var target = this, self = target.getRuntime();
// special prefix for DataURL read mode
if (op === 'readAsDataURL') {
_result = 'data:' + (blob.type || '') + ';base64,';
}
target.bind('Progress', function(e, data) {
if (data) {
_result += _formatData(data, op);
}
});
return self.shimExec.call(this, 'FileReader', 'readAsBase64', blob.uid);
},
getResult: function() {
return _result;
},
destroy: function() {
_result = null;
}
};
return (extensions.FileReader = FileReader);
});
// Included from: src/javascript/runtime/flash/file/FileReaderSync.js
/**
* FileReaderSync.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/flash/file/FileReaderSync
@private
*/
define("moxie/runtime/flash/file/FileReaderSync", [
"moxie/runtime/flash/Runtime",
"moxie/core/utils/Encode"
], function(extensions, Encode) {
function _formatData(data, op) {
switch (op) {
case 'readAsText':
return Encode.atob(data, 'utf8');
case 'readAsBinaryString':
return Encode.atob(data);
case 'readAsDataURL':
return data;
}
return null;
}
var FileReaderSync = {
read: function(op, blob) {
var result, self = this.getRuntime();
result = self.shimExec.call(this, 'FileReaderSync', 'readAsBase64', blob.uid);
if (!result) {
return null; // or throw ex
}
// special prefix for DataURL read mode
if (op === 'readAsDataURL') {
result = 'data:' + (blob.type || '') + ';base64,' + result;
}
return _formatData(result, op, blob.type);
}
};
return (extensions.FileReaderSync = FileReaderSync);
});
// Included from: src/javascript/runtime/flash/xhr/XMLHttpRequest.js
/**
* XMLHttpRequest.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/flash/xhr/XMLHttpRequest
@private
*/
define("moxie/runtime/flash/xhr/XMLHttpRequest", [
"moxie/runtime/flash/Runtime",
"moxie/core/utils/Basic",
"moxie/file/Blob",
"moxie/file/File",
"moxie/file/FileReaderSync",
"moxie/xhr/FormData",
"moxie/runtime/Transporter"
], function(extensions, Basic, Blob, File, FileReaderSync, FormData, Transporter) {
var XMLHttpRequest = {
send: function(meta, data) {
var target = this, self = target.getRuntime();
function send() {
meta.transport = self.mode;
self.shimExec.call(target, 'XMLHttpRequest', 'send', meta, data);
}
function appendBlob(name, blob) {
self.shimExec.call(target, 'XMLHttpRequest', 'appendBlob', name, blob.uid);
data = null;
send();
}
function attachBlob(blob, cb) {
var tr = new Transporter();
tr.bind("TransportingComplete", function() {
cb(this.result);
});
tr.transport(blob.getSource(), blob.type, {
ruid: self.uid
});
}
// copy over the headers if any
if (!Basic.isEmptyObj(meta.headers)) {
Basic.each(meta.headers, function(value, header) {
self.shimExec.call(target, 'XMLHttpRequest', 'setRequestHeader', header, value.toString()); // Silverlight doesn't accept integers into the arguments of type object
});
}
// transfer over multipart params and blob itself
if (data instanceof FormData) {
var blobField;
data.each(function(value, name) {
if (value instanceof Blob) {
blobField = name;
} else {
self.shimExec.call(target, 'XMLHttpRequest', 'append', name, value);
}
});
if (!data.hasBlob()) {
data = null;
send();
} else {
var blob = data.getBlob();
if (blob.isDetached()) {
attachBlob(blob, function(attachedBlob) {
blob.destroy();
appendBlob(blobField, attachedBlob);
});
} else {
appendBlob(blobField, blob);
}
}
} else if (data instanceof Blob) {
if (data.isDetached()) {
attachBlob(data, function(attachedBlob) {
data.destroy();
data = attachedBlob.uid;
send();
});
} else {
data = data.uid;
send();
}
} else {
send();
}
},
getResponse: function(responseType) {
var frs, blob, self = this.getRuntime();
blob = self.shimExec.call(this, 'XMLHttpRequest', 'getResponseAsBlob');
if (blob) {
blob = new File(self.uid, blob);
if ('blob' === responseType) {
return blob;
}
try {
frs = new FileReaderSync();
if (!!~Basic.inArray(responseType, ["", "text"])) {
return frs.readAsText(blob);
} else if ('json' === responseType && !!window.JSON) {
return JSON.parse(frs.readAsText(blob));
}
} finally {
blob.destroy();
}
}
return null;
},
abort: function(upload_complete_flag) {
var self = this.getRuntime();
self.shimExec.call(this, 'XMLHttpRequest', 'abort');
this.dispatchEvent('readystatechange');
// this.dispatchEvent('progress');
this.dispatchEvent('abort');
//if (!upload_complete_flag) {
// this.dispatchEvent('uploadprogress');
//}
}
};
return (extensions.XMLHttpRequest = XMLHttpRequest);
});
// Included from: src/javascript/runtime/flash/runtime/Transporter.js
/**
* Transporter.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/flash/runtime/Transporter
@private
*/
define("moxie/runtime/flash/runtime/Transporter", [
"moxie/runtime/flash/Runtime",
"moxie/file/Blob"
], function(extensions, Blob) {
var Transporter = {
getAsBlob: function(type) {
var self = this.getRuntime()
, blob = self.shimExec.call(this, 'Transporter', 'getAsBlob', type)
;
if (blob) {
return new Blob(self.uid, blob);
}
return null;
}
};
return (extensions.Transporter = Transporter);
});
// Included from: src/javascript/runtime/flash/image/Image.js
/**
* Image.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/flash/image/Image
@private
*/
define("moxie/runtime/flash/image/Image", [
"moxie/runtime/flash/Runtime",
"moxie/core/utils/Basic",
"moxie/runtime/Transporter",
"moxie/file/Blob",
"moxie/file/FileReaderSync"
], function(extensions, Basic, Transporter, Blob, FileReaderSync) {
var Image = {
loadFromBlob: function(blob) {
var comp = this, self = comp.getRuntime();
function exec(srcBlob) {
self.shimExec.call(comp, 'Image', 'loadFromBlob', srcBlob.uid);
comp = self = null;
}
if (blob.isDetached()) { // binary string
var tr = new Transporter();
tr.bind("TransportingComplete", function() {
exec(tr.result.getSource());
});
tr.transport(blob.getSource(), blob.type, { ruid: self.uid });
} else {
exec(blob.getSource());
}
},
loadFromImage: function(img) {
var self = this.getRuntime();
return self.shimExec.call(this, 'Image', 'loadFromImage', img.uid);
},
getAsBlob: function(type, quality) {
var self = this.getRuntime()
, blob = self.shimExec.call(this, 'Image', 'getAsBlob', type, quality)
;
if (blob) {
return new Blob(self.uid, blob);
}
return null;
},
getAsDataURL: function() {
var self = this.getRuntime()
, blob = self.Image.getAsBlob.apply(this, arguments)
, frs
;
if (!blob) {
return null;
}
frs = new FileReaderSync();
return frs.readAsDataURL(blob);
}
};
return (extensions.Image = Image);
});
// Included from: src/javascript/runtime/silverlight/Runtime.js
/**
* RunTime.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/*global ActiveXObject:true */
/**
Defines constructor for Silverlight runtime.
@class moxie/runtime/silverlight/Runtime
@private
*/
define("moxie/runtime/silverlight/Runtime", [
"moxie/core/utils/Basic",
"moxie/core/utils/Env",
"moxie/core/utils/Dom",
"moxie/core/Exceptions",
"moxie/runtime/Runtime"
], function(Basic, Env, Dom, x, Runtime) {
var type = "silverlight", extensions = {};
function isInstalled(version) {
var isVersionSupported = false, control = null, actualVer,
actualVerArray, reqVerArray, requiredVersionPart, actualVersionPart, index = 0;
try {
try {
control = new ActiveXObject('AgControl.AgControl');
if (control.IsVersionSupported(version)) {
isVersionSupported = true;
}
control = null;
} catch (e) {
var plugin = navigator.plugins["Silverlight Plug-In"];
if (plugin) {
actualVer = plugin.description;
if (actualVer === "1.0.30226.2") {
actualVer = "2.0.30226.2";
}
actualVerArray = actualVer.split(".");
while (actualVerArray.length > 3) {
actualVerArray.pop();
}
while ( actualVerArray.length < 4) {
actualVerArray.push(0);
}
reqVerArray = version.split(".");
while (reqVerArray.length > 4) {
reqVerArray.pop();
}
do {
requiredVersionPart = parseInt(reqVerArray[index], 10);
actualVersionPart = parseInt(actualVerArray[index], 10);
index++;
} while (index < reqVerArray.length && requiredVersionPart === actualVersionPart);
if (requiredVersionPart <= actualVersionPart && !isNaN(requiredVersionPart)) {
isVersionSupported = true;
}
}
}
} catch (e2) {
isVersionSupported = false;
}
return isVersionSupported;
}
/**
Constructor for the Silverlight Runtime
@class SilverlightRuntime
@extends Runtime
*/
function SilverlightRuntime(options) {
var I = this, initTimer;
options = Basic.extend({ xap_url: Env.xap_url }, options);
Runtime.call(this, options, type, {
access_binary: Runtime.capTrue,
access_image_binary: Runtime.capTrue,
display_media: Runtime.capTrue,
do_cors: Runtime.capTrue,
drag_and_drop: false,
report_upload_progress: Runtime.capTrue,
resize_image: Runtime.capTrue,
return_response_headers: function(value) {
return value && I.mode === 'client';
},
return_response_type: function(responseType) {
if (responseType !== 'json') {
return true;
} else {
return !!window.JSON;
}
},
return_status_code: function(code) {
return I.mode === 'client' || !Basic.arrayDiff(code, [200, 404]);
},
select_file: Runtime.capTrue,
select_multiple: Runtime.capTrue,
send_binary_string: Runtime.capTrue,
send_browser_cookies: function(value) {
return value && I.mode === 'browser';
},
send_custom_headers: function(value) {
return value && I.mode === 'client';
},
send_multipart: Runtime.capTrue,
slice_blob: Runtime.capTrue,
stream_upload: true,
summon_file_dialog: false,
upload_filesize: Runtime.capTrue,
use_http_method: function(methods) {
return I.mode === 'client' || !Basic.arrayDiff(methods, ['GET', 'POST']);
}
}, {
// capabilities that require specific mode
return_response_headers: function(value) {
return value ? 'client' : 'browser';
},
return_status_code: function(code) {
return Basic.arrayDiff(code, [200, 404]) ? 'client' : ['client', 'browser'];
},
send_browser_cookies: function(value) {
return value ? 'browser' : 'client';
},
send_custom_headers: function(value) {
return value ? 'client' : 'browser';
},
use_http_method: function(methods) {
return Basic.arrayDiff(methods, ['GET', 'POST']) ? 'client' : ['client', 'browser'];
}
});
// minimal requirement
if (!isInstalled('2.0.31005.0') || Env.browser === 'Opera') {
this.mode = false;
}
Basic.extend(this, {
getShim: function() {
return Dom.get(this.uid).content.Moxie;
},
shimExec: function(component, action) {
var args = [].slice.call(arguments, 2);
return I.getShim().exec(this.uid, component, action, args);
},
init : function() {
var container;
container = this.getShimContainer();
container.innerHTML = '';
// Init is dispatched by the shim
initTimer = setTimeout(function() {
if (I && !I.initialized) { // runtime might be already destroyed by this moment
I.trigger("Error", new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR));
}
}, Env.OS !== 'Windows'? 10000 : 5000); // give it more time to initialize in non Windows OS (like Mac)
},
destroy: (function(destroy) { // extend default destroy method
return function() {
destroy.call(I);
clearTimeout(initTimer); // initialization check might be still onwait
options = initTimer = destroy = I = null;
};
}(this.destroy))
}, extensions);
}
Runtime.addConstructor(type, SilverlightRuntime);
return extensions;
});
// Included from: src/javascript/runtime/silverlight/file/Blob.js
/**
* Blob.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/silverlight/file/Blob
@private
*/
define("moxie/runtime/silverlight/file/Blob", [
"moxie/runtime/silverlight/Runtime",
"moxie/core/utils/Basic",
"moxie/runtime/flash/file/Blob"
], function(extensions, Basic, Blob) {
return (extensions.Blob = Basic.extend({}, Blob));
});
// Included from: src/javascript/runtime/silverlight/file/FileInput.js
/**
* FileInput.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/silverlight/file/FileInput
@private
*/
define("moxie/runtime/silverlight/file/FileInput", [
"moxie/runtime/silverlight/Runtime"
], function(extensions) {
var FileInput = {
init: function(options) {
function toFilters(accept) {
var filter = '';
for (var i = 0; i < accept.length; i++) {
filter += (filter !== '' ? '|' : '') + accept[i].title + " | *." + accept[i].extensions.replace(/,/g, ';*.');
}
return filter;
}
this.getRuntime().shimExec.call(this, 'FileInput', 'init', toFilters(options.accept), options.name, options.multiple);
this.trigger('ready');
}
};
return (extensions.FileInput = FileInput);
});
// Included from: src/javascript/runtime/silverlight/file/FileDrop.js
/**
* FileDrop.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/silverlight/file/FileDrop
@private
*/
define("moxie/runtime/silverlight/file/FileDrop", [
"moxie/runtime/silverlight/Runtime",
"moxie/core/utils/Dom",
"moxie/core/utils/Events"
], function(extensions, Dom, Events) {
// not exactly useful, since works only in safari (...crickets...)
var FileDrop = {
init: function() {
var comp = this, self = comp.getRuntime(), dropZone;
dropZone = self.getShimContainer();
Events.addEvent(dropZone, 'dragover', function(e) {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = 'copy';
}, comp.uid);
Events.addEvent(dropZone, 'dragenter', function(e) {
e.preventDefault();
var flag = Dom.get(self.uid).dragEnter(e);
// If handled, then stop propagation of event in DOM
if (flag) {
e.stopPropagation();
}
}, comp.uid);
Events.addEvent(dropZone, 'drop', function(e) {
e.preventDefault();
var flag = Dom.get(self.uid).dragDrop(e);
// If handled, then stop propagation of event in DOM
if (flag) {
e.stopPropagation();
}
}, comp.uid);
return self.shimExec.call(this, 'FileDrop', 'init');
}
};
return (extensions.FileDrop = FileDrop);
});
// Included from: src/javascript/runtime/silverlight/file/FileReader.js
/**
* FileReader.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/silverlight/file/FileReader
@private
*/
define("moxie/runtime/silverlight/file/FileReader", [
"moxie/runtime/silverlight/Runtime",
"moxie/core/utils/Basic",
"moxie/runtime/flash/file/FileReader"
], function(extensions, Basic, FileReader) {
return (extensions.FileReader = Basic.extend({}, FileReader));
});
// Included from: src/javascript/runtime/silverlight/file/FileReaderSync.js
/**
* FileReaderSync.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/silverlight/file/FileReaderSync
@private
*/
define("moxie/runtime/silverlight/file/FileReaderSync", [
"moxie/runtime/silverlight/Runtime",
"moxie/core/utils/Basic",
"moxie/runtime/flash/file/FileReaderSync"
], function(extensions, Basic, FileReaderSync) {
return (extensions.FileReaderSync = Basic.extend({}, FileReaderSync));
});
// Included from: src/javascript/runtime/silverlight/xhr/XMLHttpRequest.js
/**
* XMLHttpRequest.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/silverlight/xhr/XMLHttpRequest
@private
*/
define("moxie/runtime/silverlight/xhr/XMLHttpRequest", [
"moxie/runtime/silverlight/Runtime",
"moxie/core/utils/Basic",
"moxie/runtime/flash/xhr/XMLHttpRequest"
], function(extensions, Basic, XMLHttpRequest) {
return (extensions.XMLHttpRequest = Basic.extend({}, XMLHttpRequest));
});
// Included from: src/javascript/runtime/silverlight/runtime/Transporter.js
/**
* Transporter.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/silverlight/runtime/Transporter
@private
*/
define("moxie/runtime/silverlight/runtime/Transporter", [
"moxie/runtime/silverlight/Runtime",
"moxie/core/utils/Basic",
"moxie/runtime/flash/runtime/Transporter"
], function(extensions, Basic, Transporter) {
return (extensions.Transporter = Basic.extend({}, Transporter));
});
// Included from: src/javascript/runtime/silverlight/image/Image.js
/**
* Image.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/silverlight/image/Image
@private
*/
define("moxie/runtime/silverlight/image/Image", [
"moxie/runtime/silverlight/Runtime",
"moxie/core/utils/Basic",
"moxie/runtime/flash/image/Image"
], function(extensions, Basic, Image) {
return (extensions.Image = Basic.extend({}, Image, {
getInfo: function() {
var self = this.getRuntime()
, grps = ['tiff', 'exif', 'gps']
, info = { meta: {} }
, rawInfo = self.shimExec.call(this, 'Image', 'getInfo')
;
if (rawInfo.meta) {
Basic.each(grps, function(grp) {
var meta = rawInfo.meta[grp]
, tag
, i
, length
, value
;
if (meta && meta.keys) {
info.meta[grp] = {};
for (i = 0, length = meta.keys.length; i < length; i++) {
tag = meta.keys[i];
value = meta[tag];
if (value) {
// convert numbers
if (/^(\d|[1-9]\d+)$/.test(value)) { // integer (make sure doesn't start with zero)
value = parseInt(value, 10);
} else if (/^\d*\.\d+$/.test(value)) { // double
value = parseFloat(value);
}
info.meta[grp][tag] = value;
}
}
}
});
}
info.width = parseInt(rawInfo.width, 10);
info.height = parseInt(rawInfo.height, 10);
info.size = parseInt(rawInfo.size, 10);
info.type = rawInfo.type;
info.name = rawInfo.name;
return info;
}
}));
});
// Included from: src/javascript/runtime/html4/Runtime.js
/**
* Runtime.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/*global File:true */
/**
Defines constructor for HTML4 runtime.
@class moxie/runtime/html4/Runtime
@private
*/
define("moxie/runtime/html4/Runtime", [
"moxie/core/utils/Basic",
"moxie/core/Exceptions",
"moxie/runtime/Runtime",
"moxie/core/utils/Env"
], function(Basic, x, Runtime, Env) {
var type = 'html4', extensions = {};
function Html4Runtime(options) {
var I = this
, Test = Runtime.capTest
, True = Runtime.capTrue
;
Runtime.call(this, options, type, {
access_binary: Test(window.FileReader || window.File && File.getAsDataURL),
access_image_binary: false,
display_media: Test(extensions.Image && (Env.can('create_canvas') || Env.can('use_data_uri_over32kb'))),
do_cors: false,
drag_and_drop: false,
filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest
return (Env.browser === 'Chrome' && Env.version >= 28) || (Env.browser === 'IE' && Env.version >= 10);
}()),
resize_image: function() {
return extensions.Image && I.can('access_binary') && Env.can('create_canvas');
},
report_upload_progress: false,
return_response_headers: false,
return_response_type: function(responseType) {
if (responseType === 'json' && !!window.JSON) {
return true;
}
return !!~Basic.inArray(responseType, ['text', 'document', '']);
},
return_status_code: function(code) {
return !Basic.arrayDiff(code, [200, 404]);
},
select_file: function() {
return Env.can('use_fileinput');
},
select_multiple: false,
send_binary_string: false,
send_custom_headers: false,
send_multipart: true,
slice_blob: false,
stream_upload: function() {
return I.can('select_file');
},
summon_file_dialog: Test(function() { // yeah... some dirty sniffing here...
return (Env.browser === 'Firefox' && Env.version >= 4) ||
(Env.browser === 'Opera' && Env.version >= 12) ||
!!~Basic.inArray(Env.browser, ['Chrome', 'Safari']);
}()),
upload_filesize: True,
use_http_method: function(methods) {
return !Basic.arrayDiff(methods, ['GET', 'POST']);
}
});
Basic.extend(this, {
init : function() {
this.trigger("Init");
},
destroy: (function(destroy) { // extend default destroy method
return function() {
destroy.call(I);
destroy = I = null;
};
}(this.destroy))
});
Basic.extend(this.getShim(), extensions);
}
Runtime.addConstructor(type, Html4Runtime);
return extensions;
});
// Included from: src/javascript/runtime/html4/file/FileInput.js
/**
* FileInput.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html4/file/FileInput
@private
*/
define("moxie/runtime/html4/file/FileInput", [
"moxie/runtime/html4/Runtime",
"moxie/core/utils/Basic",
"moxie/core/utils/Dom",
"moxie/core/utils/Events",
"moxie/core/utils/Mime",
"moxie/core/utils/Env"
], function(extensions, Basic, Dom, Events, Mime, Env) {
function FileInput() {
var _uid, _files = [], _mimes = [], _options;
function addInput() {
var comp = this, I = comp.getRuntime(), shimContainer, browseButton, currForm, form, input, uid;
uid = Basic.guid('uid_');
shimContainer = I.getShimContainer(); // we get new ref everytime to avoid memory leaks in IE
if (_uid) { // move previous form out of the view
currForm = Dom.get(_uid + '_form');
if (currForm) {
Basic.extend(currForm.style, { top: '100%' });
}
}
// build form in DOM, since innerHTML version not able to submit file for some reason
form = document.createElement('form');
form.setAttribute('id', uid + '_form');
form.setAttribute('method', 'post');
form.setAttribute('enctype', 'multipart/form-data');
form.setAttribute('encoding', 'multipart/form-data');
Basic.extend(form.style, {
overflow: 'hidden',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%'
});
input = document.createElement('input');
input.setAttribute('id', uid);
input.setAttribute('type', 'file');
input.setAttribute('name', _options.name || 'Filedata');
input.setAttribute('accept', _mimes.join(','));
Basic.extend(input.style, {
fontSize: '999px',
opacity: 0
});
form.appendChild(input);
shimContainer.appendChild(form);
// prepare file input to be placed underneath the browse_button element
Basic.extend(input.style, {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%'
});
if (Env.browser === 'IE' && Env.version < 10) {
Basic.extend(input.style, {
filter : "progid:DXImageTransform.Microsoft.Alpha(opacity=0)"
});
}
input.onchange = function() { // there should be only one handler for this
var file;
if (!this.value) {
return;
}
if (this.files) {
file = this.files[0];
} else {
file = {
name: this.value
};
}
_files = [file];
this.onchange = function() {}; // clear event handler
addInput.call(comp);
// after file is initialized as o.File, we need to update form and input ids
comp.bind('change', function onChange() {
var input = Dom.get(uid), form = Dom.get(uid + '_form'), file;
comp.unbind('change', onChange);
if (comp.files.length && input && form) {
file = comp.files[0];
input.setAttribute('id', file.uid);
form.setAttribute('id', file.uid + '_form');
// set upload target
form.setAttribute('target', file.uid + '_iframe');
}
input = form = null;
}, 998);
input = form = null;
comp.trigger('change');
};
// route click event to the input
if (I.can('summon_file_dialog')) {
browseButton = Dom.get(_options.browse_button);
Events.removeEvent(browseButton, 'click', comp.uid);
Events.addEvent(browseButton, 'click', function(e) {
if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file]
input.click();
}
e.preventDefault();
}, comp.uid);
}
_uid = uid;
shimContainer = currForm = browseButton = null;
}
Basic.extend(this, {
init: function(options) {
var comp = this, I = comp.getRuntime(), shimContainer;
// figure out accept string
_options = options;
_mimes = options.accept.mimes || Mime.extList2mimes(options.accept, I.can('filter_by_extension'));
shimContainer = I.getShimContainer();
(function() {
var browseButton, zIndex, top;
browseButton = Dom.get(options.browse_button);
// Route click event to the input[type=file] element for browsers that support such behavior
if (I.can('summon_file_dialog')) {
if (Dom.getStyle(browseButton, 'position') === 'static') {
browseButton.style.position = 'relative';
}
zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1;
browseButton.style.zIndex = zIndex;
shimContainer.style.zIndex = zIndex - 1;
}
/* Since we have to place input[type=file] on top of the browse_button for some browsers,
browse_button loses interactivity, so we restore it here */
top = I.can('summon_file_dialog') ? browseButton : shimContainer;
Events.addEvent(top, 'mouseover', function() {
comp.trigger('mouseenter');
}, comp.uid);
Events.addEvent(top, 'mouseout', function() {
comp.trigger('mouseleave');
}, comp.uid);
Events.addEvent(top, 'mousedown', function() {
comp.trigger('mousedown');
}, comp.uid);
Events.addEvent(Dom.get(options.container), 'mouseup', function() {
comp.trigger('mouseup');
}, comp.uid);
browseButton = null;
}());
addInput.call(this);
shimContainer = null;
// trigger ready event asynchronously
comp.trigger({
type: 'ready',
async: true
});
},
getFiles: function() {
return _files;
},
disable: function(state) {
var input;
if ((input = Dom.get(_uid))) {
input.disabled = !!state;
}
},
destroy: function() {
var I = this.getRuntime()
, shim = I.getShim()
, shimContainer = I.getShimContainer()
;
Events.removeAllEvents(shimContainer, this.uid);
Events.removeAllEvents(_options && Dom.get(_options.container), this.uid);
Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid);
if (shimContainer) {
shimContainer.innerHTML = '';
}
shim.removeInstance(this.uid);
_uid = _files = _mimes = _options = shimContainer = shim = null;
}
});
}
return (extensions.FileInput = FileInput);
});
// Included from: src/javascript/runtime/html4/file/FileReader.js
/**
* FileReader.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html4/file/FileReader
@private
*/
define("moxie/runtime/html4/file/FileReader", [
"moxie/runtime/html4/Runtime",
"moxie/runtime/html5/file/FileReader"
], function(extensions, FileReader) {
return (extensions.FileReader = FileReader);
});
// Included from: src/javascript/runtime/html4/xhr/XMLHttpRequest.js
/**
* XMLHttpRequest.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html4/xhr/XMLHttpRequest
@private
*/
define("moxie/runtime/html4/xhr/XMLHttpRequest", [
"moxie/runtime/html4/Runtime",
"moxie/core/utils/Basic",
"moxie/core/utils/Dom",
"moxie/core/utils/Url",
"moxie/core/Exceptions",
"moxie/core/utils/Events",
"moxie/file/Blob",
"moxie/xhr/FormData"
], function(extensions, Basic, Dom, Url, x, Events, Blob, FormData) {
function XMLHttpRequest() {
var _status, _response, _iframe;
function cleanup(cb) {
var target = this, uid, form, inputs, i, hasFile = false;
if (!_iframe) {
return;
}
uid = _iframe.id.replace(/_iframe$/, '');
form = Dom.get(uid + '_form');
if (form) {
inputs = form.getElementsByTagName('input');
i = inputs.length;
while (i--) {
switch (inputs[i].getAttribute('type')) {
case 'hidden':
inputs[i].parentNode.removeChild(inputs[i]);
break;
case 'file':
hasFile = true; // flag the case for later
break;
}
}
inputs = [];
if (!hasFile) { // we need to keep the form for sake of possible retries
form.parentNode.removeChild(form);
}
form = null;
}
// without timeout, request is marked as canceled (in console)
setTimeout(function() {
Events.removeEvent(_iframe, 'load', target.uid);
if (_iframe.parentNode) { // #382
_iframe.parentNode.removeChild(_iframe);
}
// check if shim container has any other children, if - not, remove it as well
var shimContainer = target.getRuntime().getShimContainer();
if (!shimContainer.children.length) {
shimContainer.parentNode.removeChild(shimContainer);
}
shimContainer = _iframe = null;
cb();
}, 1);
}
Basic.extend(this, {
send: function(meta, data) {
var target = this, I = target.getRuntime(), uid, form, input, blob;
_status = _response = null;
function createIframe() {
var container = I.getShimContainer() || document.body
, temp = document.createElement('div')
;
// IE 6 won't be able to set the name using setAttribute or iframe.name
temp.innerHTML = '';
_iframe = temp.firstChild;
container.appendChild(_iframe);
/* _iframe.onreadystatechange = function() {
console.info(_iframe.readyState);
};*/
Events.addEvent(_iframe, 'load', function() { // _iframe.onload doesn't work in IE lte 8
var el;
try {
el = _iframe.contentWindow.document || _iframe.contentDocument || window.frames[_iframe.id].document;
// try to detect some standard error pages
if (/^4(0[0-9]|1[0-7]|2[2346])\s/.test(el.title)) { // test if title starts with 4xx HTTP error
_status = el.title.replace(/^(\d+).*$/, '$1');
} else {
_status = 200;
// get result
_response = Basic.trim(el.body.innerHTML);
// we need to fire these at least once
target.trigger({
type: 'progress',
loaded: _response.length,
total: _response.length
});
if (blob) { // if we were uploading a file
target.trigger({
type: 'uploadprogress',
loaded: blob.size || 1025,
total: blob.size || 1025
});
}
}
} catch (ex) {
if (Url.hasSameOrigin(meta.url)) {
// if response is sent with error code, iframe in IE gets redirected to res://ieframe.dll/http_x.htm
// which obviously results to cross domain error (wtf?)
_status = 404;
} else {
cleanup.call(target, function() {
target.trigger('error');
});
return;
}
}
cleanup.call(target, function() {
target.trigger('load');
});
}, target.uid);
} // end createIframe
// prepare data to be sent and convert if required
if (data instanceof FormData && data.hasBlob()) {
blob = data.getBlob();
uid = blob.uid;
input = Dom.get(uid);
form = Dom.get(uid + '_form');
if (!form) {
throw new x.DOMException(x.DOMException.NOT_FOUND_ERR);
}
} else {
uid = Basic.guid('uid_');
form = document.createElement('form');
form.setAttribute('id', uid + '_form');
form.setAttribute('method', meta.method);
form.setAttribute('enctype', 'multipart/form-data');
form.setAttribute('encoding', 'multipart/form-data');
form.setAttribute('target', uid + '_iframe');
I.getShimContainer().appendChild(form);
}
if (data instanceof FormData) {
data.each(function(value, name) {
if (value instanceof Blob) {
if (input) {
input.setAttribute('name', name);
}
} else {
var hidden = document.createElement('input');
Basic.extend(hidden, {
type : 'hidden',
name : name,
value : value
});
// make sure that input[type="file"], if it's there, comes last
if (input) {
form.insertBefore(hidden, input);
} else {
form.appendChild(hidden);
}
}
});
}
// set destination url
form.setAttribute("action", meta.url);
createIframe();
form.submit();
target.trigger('loadstart');
},
getStatus: function() {
return _status;
},
getResponse: function(responseType) {
if ('json' === responseType) {
// strip off
..
tags that might be enclosing the response
if (Basic.typeOf(_response) === 'string' && !!window.JSON) {
try {
return JSON.parse(_response.replace(/^\s*
]*>/, '').replace(/<\/pre>\s*$/, ''));
} catch (ex) {
return null;
}
}
} else if ('document' === responseType) {
}
return _response;
},
abort: function() {
var target = this;
if (_iframe && _iframe.contentWindow) {
if (_iframe.contentWindow.stop) { // FireFox/Safari/Chrome
_iframe.contentWindow.stop();
} else if (_iframe.contentWindow.document.execCommand) { // IE
_iframe.contentWindow.document.execCommand('Stop');
} else {
_iframe.src = "about:blank";
}
}
cleanup.call(this, function() {
// target.dispatchEvent('readystatechange');
target.dispatchEvent('abort');
});
}
});
}
return (extensions.XMLHttpRequest = XMLHttpRequest);
});
// Included from: src/javascript/runtime/html4/image/Image.js
/**
* Image.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/**
@class moxie/runtime/html4/image/Image
@private
*/
define("moxie/runtime/html4/image/Image", [
"moxie/runtime/html4/Runtime",
"moxie/runtime/html5/image/Image"
], function(extensions, Image) {
return (extensions.Image = Image);
});
expose(["moxie/core/utils/Basic","moxie/core/I18n","moxie/core/utils/Mime","moxie/core/utils/Env","moxie/core/utils/Dom","moxie/core/Exceptions","moxie/core/EventTarget","moxie/core/utils/Encode","moxie/runtime/Runtime","moxie/runtime/RuntimeClient","moxie/file/Blob","moxie/file/File","moxie/file/FileInput","moxie/file/FileDrop","moxie/runtime/RuntimeTarget","moxie/file/FileReader","moxie/core/utils/Url","moxie/file/FileReaderSync","moxie/xhr/FormData","moxie/xhr/XMLHttpRequest","moxie/runtime/Transporter","moxie/image/Image","moxie/core/utils/Events"]);
})(this);/**
* o.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/*global moxie:true */
/**
Globally exposed namespace with the most frequently used public classes and handy methods.
@class o
@static
@private
*/
(function(exports) {
"use strict";
var o = {}, inArray = exports.moxie.core.utils.Basic.inArray;
// directly add some public classes
// (we do it dynamically here, since for custom builds we cannot know beforehand what modules were included)
(function addAlias(ns) {
var name, itemType;
for (name in ns) {
itemType = typeof(ns[name]);
if (itemType === 'object' && !~inArray(name, ['Exceptions', 'Env', 'Mime'])) {
addAlias(ns[name]);
} else if (itemType === 'function') {
o[name] = ns[name];
}
}
})(exports.moxie);
// add some manually
o.Env = exports.moxie.core.utils.Env;
o.Mime = exports.moxie.core.utils.Mime;
o.Exceptions = exports.moxie.core.Exceptions;
// expose globally
exports.mOxie = o;
if (!exports.o) {
exports.o = o;
}
return o;
})(this);
// source --> https://wp.glencommunity.org/wp-content/plugins/buddyboss-media/assets/vendor/plupload2/plupload.dev.js?ver=2.1.2
/**
* Plupload - multi-runtime File Uploader
* v2.1.2
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*
* Date: 2014-05-14
*/
/**
* Plupload.js
*
* Copyright 2013, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
/*global mOxie:true */
;(function(window, o, undef) {
var delay = window.setTimeout
, fileFilters = {}
;
// convert plupload features to caps acceptable by mOxie
function normalizeCaps(settings) {
var features = settings.required_features, caps = {};
function resolve(feature, value, strict) {
// Feature notation is deprecated, use caps (this thing here is required for backward compatibility)
var map = {
chunks: 'slice_blob',
jpgresize: 'send_binary_string',
pngresize: 'send_binary_string',
progress: 'report_upload_progress',
multi_selection: 'select_multiple',
dragdrop: 'drag_and_drop',
drop_element: 'drag_and_drop',
headers: 'send_custom_headers',
urlstream_upload: 'send_binary_string',
canSendBinary: 'send_binary',
triggerDialog: 'summon_file_dialog'
};
if (map[feature]) {
caps[map[feature]] = value;
} else if (!strict) {
caps[feature] = value;
}
}
if (typeof(features) === 'string') {
plupload.each(features.split(/\s*,\s*/), function(feature) {
resolve(feature, true);
});
} else if (typeof(features) === 'object') {
plupload.each(features, function(value, feature) {
resolve(feature, value);
});
} else if (features === true) {
// check settings for required features
if (settings.chunk_size > 0) {
caps.slice_blob = true;
}
if (settings.resize.enabled || !settings.multipart) {
caps.send_binary_string = true;
}
plupload.each(settings, function(value, feature) {
resolve(feature, !!value, true); // strict check
});
}
return caps;
}
/**
* @module plupload
* @static
*/
var plupload = {
/**
* Plupload version will be replaced on build.
*
* @property VERSION
* @for Plupload
* @static
* @final
*/
VERSION : '2.1.2',
/**
* Inital state of the queue and also the state ones it's finished all it's uploads.
*
* @property STOPPED
* @static
* @final
*/
STOPPED : 1,
/**
* Upload process is running
*
* @property STARTED
* @static
* @final
*/
STARTED : 2,
/**
* File is queued for upload
*
* @property QUEUED
* @static
* @final
*/
QUEUED : 1,
/**
* File is being uploaded
*
* @property UPLOADING
* @static
* @final
*/
UPLOADING : 2,
/**
* File has failed to be uploaded
*
* @property FAILED
* @static
* @final
*/
FAILED : 4,
/**
* File has been uploaded successfully
*
* @property DONE
* @static
* @final
*/
DONE : 5,
// Error constants used by the Error event
/**
* Generic error for example if an exception is thrown inside Silverlight.
*
* @property GENERIC_ERROR
* @static
* @final
*/
GENERIC_ERROR : -100,
/**
* HTTP transport error. For example if the server produces a HTTP status other than 200.
*
* @property HTTP_ERROR
* @static
* @final
*/
HTTP_ERROR : -200,
/**
* Generic I/O error. For example if it wasn't possible to open the file stream on local machine.
*
* @property IO_ERROR
* @static
* @final
*/
IO_ERROR : -300,
/**
* @property SECURITY_ERROR
* @static
* @final
*/
SECURITY_ERROR : -400,
/**
* Initialization error. Will be triggered if no runtime was initialized.
*
* @property INIT_ERROR
* @static
* @final
*/
INIT_ERROR : -500,
/**
* File size error. If the user selects a file that is too large it will be blocked and an error of this type will be triggered.
*
* @property FILE_SIZE_ERROR
* @static
* @final
*/
FILE_SIZE_ERROR : -600,
/**
* File extension error. If the user selects a file that isn't valid according to the filters setting.
*
* @property FILE_EXTENSION_ERROR
* @static
* @final
*/
FILE_EXTENSION_ERROR : -601,
/**
* Duplicate file error. If prevent_duplicates is set to true and user selects the same file again.
*
* @property FILE_DUPLICATE_ERROR
* @static
* @final
*/
FILE_DUPLICATE_ERROR : -602,
/**
* Runtime will try to detect if image is proper one. Otherwise will throw this error.
*
* @property IMAGE_FORMAT_ERROR
* @static
* @final
*/
IMAGE_FORMAT_ERROR : -700,
/**
* While working on files runtime may run out of memory and will throw this error.
*
* @since 2.1.2
* @property MEMORY_ERROR
* @static
* @final
*/
MEMORY_ERROR : -701,
/**
* Each runtime has an upper limit on a dimension of the image it can handle. If bigger, will throw this error.
*
* @property IMAGE_DIMENSIONS_ERROR
* @static
* @final
*/
IMAGE_DIMENSIONS_ERROR : -702,
/**
* Mime type lookup table.
*
* @property mimeTypes
* @type Object
* @final
*/
mimeTypes : o.mimes,
/**
* In some cases sniffing is the only way around :(
*/
ua: o.ua,
/**
* Gets the true type of the built-in object (better version of typeof).
* @credits Angus Croll (http://javascriptweblog.wordpress.com/)
*
* @method typeOf
* @static
* @param {Object} o Object to check.
* @return {String} Object [[Class]]
*/
typeOf: o.typeOf,
/**
* Extends the specified object with another object.
*
* @method extend
* @static
* @param {Object} target Object to extend.
* @param {Object..} obj Multiple objects to extend with.
* @return {Object} Same as target, the extended object.
*/
extend : o.extend,
/**
* Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers.
* The only way a user would be able to get the same ID is if the two persons at the same exact milisecond manages
* to get 5 the same random numbers between 0-65535 it also uses a counter so each call will be guaranteed to be page unique.
* It's more probable for the earth to be hit with an ansteriod. You can also if you want to be 100% sure set the plupload.guidPrefix property
* to an user unique key.
*
* @method guid
* @static
* @return {String} Virtually unique id.
*/
guid : o.guid,
/**
* Get array of DOM Elements by their ids.
*
* @method get
* @for Utils
* @param {String} id Identifier of the DOM Element
* @return {Array}
*/
get : function get(ids) {
var els = [], el;
if (o.typeOf(ids) !== 'array') {
ids = [ids];
}
var i = ids.length;
while (i--) {
el = o.get(ids[i]);
if (el) {
els.push(el);
}
}
return els.length ? els : null;
},
/**
* Executes the callback function for each item in array/object. If you return false in the
* callback it will break the loop.
*
* @method each
* @static
* @param {Object} obj Object to iterate.
* @param {function} callback Callback function to execute for each item.
*/
each : o.each,
/**
* Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields.
*
* @method getPos
* @static
* @param {Element} node HTML element or element id to get x, y position from.
* @param {Element} root Optional root element to stop calculations at.
* @return {object} Absolute position of the specified element object with x, y fields.
*/
getPos : o.getPos,
/**
* Returns the size of the specified node in pixels.
*
* @method getSize
* @static
* @param {Node} node Node to get the size of.
* @return {Object} Object with a w and h property.
*/
getSize : o.getSize,
/**
* Encodes the specified string.
*
* @method xmlEncode
* @static
* @param {String} s String to encode.
* @return {String} Encoded string.
*/
xmlEncode : function(str) {
var xmlEncodeChars = {'<' : 'lt', '>' : 'gt', '&' : 'amp', '"' : 'quot', '\'' : '#39'}, xmlEncodeRegExp = /[<>&\"\']/g;
return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) {
return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr;
}) : str;
},
/**
* Forces anything into an array.
*
* @method toArray
* @static
* @param {Object} obj Object with length field.
* @return {Array} Array object containing all items.
*/
toArray : o.toArray,
/**
* Find an element in array and return it's index if present, otherwise return -1.
*
* @method inArray
* @static
* @param {mixed} needle Element to find
* @param {Array} array
* @return {Int} Index of the element, or -1 if not found
*/
inArray : o.inArray,
/**
* Extends the language pack object with new items.
*
* @method addI18n
* @static
* @param {Object} pack Language pack items to add.
* @return {Object} Extended language pack object.
*/
addI18n : o.addI18n,
/**
* Translates the specified string by checking for the english string in the language pack lookup.
*
* @method translate
* @static
* @param {String} str String to look for.
* @return {String} Translated string or the input string if it wasn't found.
*/
translate : o.translate,
/**
* Checks if object is empty.
*
* @method isEmptyObj
* @static
* @param {Object} obj Object to check.
* @return {Boolean}
*/
isEmptyObj : o.isEmptyObj,
/**
* Checks if specified DOM element has specified class.
*
* @method hasClass
* @static
* @param {Object} obj DOM element like object to add handler to.
* @param {String} name Class name
*/
hasClass : o.hasClass,
/**
* Adds specified className to specified DOM element.
*
* @method addClass
* @static
* @param {Object} obj DOM element like object to add handler to.
* @param {String} name Class name
*/
addClass : o.addClass,
/**
* Removes specified className from specified DOM element.
*
* @method removeClass
* @static
* @param {Object} obj DOM element like object to add handler to.
* @param {String} name Class name
*/
removeClass : o.removeClass,
/**
* Returns a given computed style of a DOM element.
*
* @method getStyle
* @static
* @param {Object} obj DOM element like object.
* @param {String} name Style you want to get from the DOM element
*/
getStyle : o.getStyle,
/**
* Adds an event handler to the specified object and store reference to the handler
* in objects internal Plupload registry (@see removeEvent).
*
* @method addEvent
* @static
* @param {Object} obj DOM element like object to add handler to.
* @param {String} name Name to add event listener to.
* @param {Function} callback Function to call when event occurs.
* @param {String} (optional) key that might be used to add specifity to the event record.
*/
addEvent : o.addEvent,
/**
* Remove event handler from the specified object. If third argument (callback)
* is not specified remove all events with the specified name.
*
* @method removeEvent
* @static
* @param {Object} obj DOM element to remove event listener(s) from.
* @param {String} name Name of event listener to remove.
* @param {Function|String} (optional) might be a callback or unique key to match.
*/
removeEvent: o.removeEvent,
/**
* Remove all kind of events from the specified object
*
* @method removeAllEvents
* @static
* @param {Object} obj DOM element to remove event listeners from.
* @param {String} (optional) unique key to match, when removing events.
*/
removeAllEvents: o.removeAllEvents,
/**
* Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _.
*
* @method cleanName
* @static
* @param {String} s String to clean up.
* @return {String} Cleaned string.
*/
cleanName : function(name) {
var i, lookup;
// Replace diacritics
lookup = [
/[\300-\306]/g, 'A', /[\340-\346]/g, 'a',
/\307/g, 'C', /\347/g, 'c',
/[\310-\313]/g, 'E', /[\350-\353]/g, 'e',
/[\314-\317]/g, 'I', /[\354-\357]/g, 'i',
/\321/g, 'N', /\361/g, 'n',
/[\322-\330]/g, 'O', /[\362-\370]/g, 'o',
/[\331-\334]/g, 'U', /[\371-\374]/g, 'u'
];
for (i = 0; i < lookup.length; i += 2) {
name = name.replace(lookup[i], lookup[i + 1]);
}
// Replace whitespace
name = name.replace(/\s+/g, '_');
// Remove anything else
name = name.replace(/[^a-z0-9_\-\.]+/gi, '');
return name;
},
/**
* Builds a full url out of a base URL and an object with items to append as query string items.
*
* @method buildUrl
* @static
* @param {String} url Base URL to append query string items to.
* @param {Object} items Name/value object to serialize as a querystring.
* @return {String} String with url + serialized query string items.
*/
buildUrl : function(url, items) {
var query = '';
plupload.each(items, function(value, name) {
query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value);
});
if (query) {
url += (url.indexOf('?') > 0 ? '&' : '?') + query;
}
return url;
},
/**
* Formats the specified number as a size string for example 1024 becomes 1 KB.
*
* @method formatSize
* @static
* @param {Number} size Size to format as string.
* @return {String} Formatted size string.
*/
formatSize : function(size) {
if (size === undef || /\D/.test(size)) {
return plupload.translate('N/A');
}
function round(num, precision) {
return Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision);
}
var boundary = Math.pow(1024, 4);
// TB
if (size > boundary) {
return round(size / boundary, 1) + " " + plupload.translate('tb');
}
// GB
if (size > (boundary/=1024)) {
return round(size / boundary, 1) + " " + plupload.translate('gb');
}
// MB
if (size > (boundary/=1024)) {
return round(size / boundary, 1) + " " + plupload.translate('mb');
}
// KB
if (size > 1024) {
return Math.round(size / 1024) + " " + plupload.translate('kb');
}
return size + " " + plupload.translate('b');
},
/**
* Parses the specified size string into a byte value. For example 10kb becomes 10240.
*
* @method parseSize
* @static
* @param {String|Number} size String to parse or number to just pass through.
* @return {Number} Size in bytes.
*/
parseSize : o.parseSizeStr,
/**
* A way to predict what runtime will be choosen in the current environment with the
* specified settings.
*
* @method predictRuntime
* @static
* @param {Object|String} config Plupload settings to check
* @param {String} [runtimes] Comma-separated list of runtimes to check against
* @return {String} Type of compatible runtime
*/
predictRuntime : function(config, runtimes) {
var up, runtime;
up = new plupload.Uploader(config);
runtime = o.Runtime.thatCan(up.getOption().required_features, runtimes || config.runtimes);
up.destroy();
return runtime;
},
/**
* Registers a filter that will be executed for each file added to the queue.
* If callback returns false, file will not be added.
*
* Callback receives two arguments: a value for the filter as it was specified in settings.filters
* and a file to be filtered. Callback is executed in the context of uploader instance.
*
* @method addFileFilter
* @static
* @param {String} name Name of the filter by which it can be referenced in settings.filters
* @param {String} cb Callback - the actual routine that every added file must pass
*/
addFileFilter: function(name, cb) {
fileFilters[name] = cb;
}
};
plupload.addFileFilter('mime_types', function(filters, file, cb) {
if (filters.length && !filters.regexp.test(file.name)) {
this.trigger('Error', {
code : plupload.FILE_EXTENSION_ERROR,
message : plupload.translate('File extension error.'),
file : file
});
cb(false);
} else {
cb(true);
}
});
plupload.addFileFilter('max_file_size', function(maxSize, file, cb) {
var undef;
maxSize = plupload.parseSize(maxSize);
// Invalid file size
if (file.size !== undef && maxSize && file.size > maxSize) {
this.trigger('Error', {
code : plupload.FILE_SIZE_ERROR,
message : plupload.translate('File size error.'),
file : file
});
cb(false);
} else {
cb(true);
}
});
plupload.addFileFilter('prevent_duplicates', function(value, file, cb) {
if (value) {
var ii = this.files.length;
while (ii--) {
// Compare by name and size (size might be 0 or undefined, but still equivalent for both)
if (file.name === this.files[ii].name && file.size === this.files[ii].size) {
this.trigger('Error', {
code : plupload.FILE_DUPLICATE_ERROR,
message : plupload.translate('Duplicate file error.'),
file : file
});
cb(false);
return;
}
}
}
cb(true);
});
/**
@class Uploader
@constructor
@param {Object} settings For detailed information about each option check documentation.
@param {String|DOMElement} settings.browse_button id of the DOM element or DOM element itself to use as file dialog trigger.
@param {String} settings.url URL of the server-side upload handler.
@param {Number|String} [settings.chunk_size=0] Chunk size in bytes to slice the file into. Shorcuts with b, kb, mb, gb, tb suffixes also supported. `e.g. 204800 or "204800b" or "200kb"`. By default - disabled.
@param {Boolean} [settings.send_chunk_number=true] Whether to send chunks and chunk numbers, or total and offset bytes.
@param {String} [settings.container] id of the DOM element to use as a container for uploader structures. Defaults to document.body.
@param {String|DOMElement} [settings.drop_element] id of the DOM element or DOM element itself to use as a drop zone for Drag-n-Drop.
@param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message.
@param {Object} [settings.filters={}] Set of file type filters.
@param {Array} [settings.filters.mime_types=[]] List of file types to accept, each one defined by title and list of extensions. `e.g. {title : "Image files", extensions : "jpg,jpeg,gif,png"}`. Dispatches `plupload.FILE_EXTENSION_ERROR`
@param {String|Number} [settings.filters.max_file_size=0] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`.
@param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`.
@param {String} [settings.flash_swf_url] URL of the Flash swf.
@param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs.
@param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event.
@param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message.
@param {Object} [settings.multipart_params] Hash of key/value pairs to send with every file upload.
@param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog.
@param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess.
@param {Object} [settings.resize] Enable resizng of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}`
@param {Number} [settings.resize.width] If image is bigger, it will be resized.
@param {Number} [settings.resize.height] If image is bigger, it will be resized.
@param {Number} [settings.resize.quality=90] Compression quality for jpegs (1-100).
@param {Boolean} [settings.resize.crop=false] Whether to crop images to exact dimensions. By default they will be resized proportionally.
@param {String} [settings.runtimes="html5,flash,silverlight,html4"] Comma separated list of runtimes, that Plupload will try in turn, moving to the next if previous fails.
@param {String} [settings.silverlight_xap_url] URL of the Silverlight xap.
@param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files.
@param {Boolean} [settings.send_file_name=true] Whether to send file name as additional argument - 'name' (required for chunked uploads and some other cases where file name cannot be sent via normal ways).
*/
plupload.Uploader = function(options) {
/**
* Fires when the current RunTime has been initialized.
*
* @event Init
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires after the init event incase you need to perform actions there.
*
* @event PostInit
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires when the option is changed in via uploader.setOption().
*
* @event OptionChanged
* @since 2.1
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {String} name Name of the option that was changed
* @param {Mixed} value New value for the specified option
* @param {Mixed} oldValue Previous value of the option
*/
/**
* Fires when the silverlight/flash or other shim needs to move.
*
* @event Refresh
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires when the overall state is being changed for the upload queue.
*
* @event StateChanged
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires when browse_button is clicked and browse dialog shows.
*
* @event Browse
* @since 2.1.2
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires for every filtered file before it is added to the queue.
*
* @event FileFiltered
* @since 2.1
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file Another file that has to be added to the queue.
*/
/**
* Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance.
*
* @event QueueChanged
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires after files were filtered and added to the queue.
*
* @event FilesAdded
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {Array} files Array of file objects that were added to queue by the user.
*/
/**
* Fires when file is removed from the queue.
*
* @event FilesRemoved
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {Array} files Array of files that got removed.
*/
/**
* Fires when just before a file is uploaded. This event enables you to override settings
* on the uploader instance before the file is uploaded.
*
* @event BeforeUpload
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file File to be uploaded.
*/
/**
* Fires when a file is to be uploaded by the runtime.
*
* @event UploadFile
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file File to be uploaded.
*/
/**
* Fires while a file is being uploaded. Use this event to update the current file upload progress.
*
* @event UploadProgress
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file File that is currently being uploaded.
*/
/**
* Fires when file chunk is uploaded.
*
* @event ChunkUploaded
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file File that the chunk was uploaded for.
* @param {Object} response Object with response properties.
*/
/**
* Fires when a file is successfully uploaded.
*
* @event FileUploaded
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file File that was uploaded.
* @param {Object} response Object with response properties.
*/
/**
* Fires when all files in a queue are uploaded.
*
* @event UploadComplete
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {Array} files Array of file objects that was added to queue/selected by the user.
*/
/**
* Fires when a error occurs.
*
* @event Error
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {Object} error Contains code, message and sometimes file and other details.
*/
/**
* Fires when destroy method is called.
*
* @event Destroy
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
var uid = plupload.guid()
, settings
, files = []
, preferred_caps = {}
, fileInputs = []
, fileDrops = []
, startTime
, total
, disabled = false
, xhr
;
// Private methods
function uploadNext() {
var file, count = 0, i;
if (this.state == plupload.STARTED) {
// Find first QUEUED file
for (i = 0; i < files.length; i++) {
if (!file && files[i].status == plupload.QUEUED) {
file = files[i];
if (this.trigger("BeforeUpload", file)) {
file.status = plupload.UPLOADING;
this.trigger("UploadFile", file);
}
} else {
count++;
}
}
// All files are DONE or FAILED
if (count == files.length) {
if (this.state !== plupload.STOPPED) {
this.state = plupload.STOPPED;
this.trigger("StateChanged");
}
this.trigger("UploadComplete", files);
}
}
}
function calcFile(file) {
file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100;
calc();
}
function calc() {
var i, file;
// Reset stats
total.reset();
// Check status, size, loaded etc on all files
for (i = 0; i < files.length; i++) {
file = files[i];
if (file.size !== undef) {
// We calculate totals based on original file size
total.size += file.origSize;
// Since we cannot predict file size after resize, we do opposite and
// interpolate loaded amount to match magnitude of total
total.loaded += file.loaded * file.origSize / file.size;
} else {
total.size = undef;
}
if (file.status == plupload.DONE) {
total.uploaded++;
} else if (file.status == plupload.FAILED) {
total.failed++;
} else {
total.queued++;
}
}
// If we couldn't calculate a total file size then use the number of files to calc percent
if (total.size === undef) {
total.percent = files.length > 0 ? Math.ceil(total.uploaded / files.length * 100) : 0;
} else {
total.bytesPerSec = Math.ceil(total.loaded / ((+new Date() - startTime || 1) / 1000.0));
total.percent = total.size > 0 ? Math.ceil(total.loaded / total.size * 100) : 0;
}
}
function getRUID() {
var ctrl = fileInputs[0] || fileDrops[0];
if (ctrl) {
return ctrl.getRuntime().uid;
}
return false;
}
function runtimeCan(file, cap) {
if (file.ruid) {
var info = o.Runtime.getInfo(file.ruid);
if (info) {
return info.can(cap);
}
}
return false;
}
function bindEventListeners() {
this.bind('FilesAdded FilesRemoved', function(up) {
up.trigger('QueueChanged');
up.refresh();
});
this.bind('CancelUpload', onCancelUpload);
this.bind('BeforeUpload', onBeforeUpload);
this.bind('UploadFile', onUploadFile);
this.bind('UploadProgress', onUploadProgress);
this.bind('StateChanged', onStateChanged);
this.bind('QueueChanged', calc);
this.bind('Error', onError);
this.bind('FileUploaded', onFileUploaded);
this.bind('Destroy', onDestroy);
}
function initControls(settings, cb) {
var self = this, inited = 0, queue = [];
// common settings
var options = {
runtime_order: settings.runtimes,
required_caps: settings.required_features,
preferred_caps: preferred_caps,
swf_url: settings.flash_swf_url,
xap_url: settings.silverlight_xap_url
};
// add runtime specific options if any
plupload.each(settings.runtimes.split(/\s*,\s*/), function(runtime) {
if (settings[runtime]) {
options[runtime] = settings[runtime];
}
});
// initialize file pickers - there can be many
if (settings.browse_button) {
plupload.each(settings.browse_button, function(el) {
queue.push(function(cb) {
var fileInput = new o.FileInput(plupload.extend({}, options, {
accept: settings.filters.mime_types,
name: settings.file_data_name,
multiple: settings.multi_selection,
container: settings.container,
browse_button: el
}));
fileInput.onready = function() {
var info = o.Runtime.getInfo(this.ruid);
// for backward compatibility
o.extend(self.features, {
chunks: info.can('slice_blob'),
multipart: info.can('send_multipart'),
multi_selection: info.can('select_multiple')
});
inited++;
fileInputs.push(this);
cb();
};
fileInput.onchange = function() {
self.addFile(this.files);
};
fileInput.bind('mouseenter mouseleave mousedown mouseup', function(e) {
if (!disabled) {
if (settings.browse_button_hover) {
if ('mouseenter' === e.type) {
o.addClass(el, settings.browse_button_hover);
} else if ('mouseleave' === e.type) {
o.removeClass(el, settings.browse_button_hover);
}
}
if (settings.browse_button_active) {
if ('mousedown' === e.type) {
o.addClass(el, settings.browse_button_active);
} else if ('mouseup' === e.type) {
o.removeClass(el, settings.browse_button_active);
}
}
}
});
fileInput.bind('mousedown', function() {
self.trigger('Browse');
});
fileInput.bind('error runtimeerror', function() {
fileInput = null;
cb();
});
fileInput.init();
});
});
}
// initialize drop zones
if (settings.drop_element) {
plupload.each(settings.drop_element, function(el) {
queue.push(function(cb) {
var fileDrop = new o.FileDrop(plupload.extend({}, options, {
drop_zone: el
}));
fileDrop.onready = function() {
var info = o.Runtime.getInfo(this.ruid);
self.features.dragdrop = info.can('drag_and_drop'); // for backward compatibility
inited++;
fileDrops.push(this);
cb();
};
fileDrop.ondrop = function() {
self.addFile(this.files);
};
fileDrop.bind('error runtimeerror', function() {
fileDrop = null;
cb();
});
fileDrop.init();
});
});
}
o.inSeries(queue, function() {
if (typeof(cb) === 'function') {
cb(inited);
}
});
}
function resizeImage(blob, params, cb) {
var img = new o.Image();
try {
img.onload = function() {
// no manipulation required if...
if (params.width > this.width &&
params.height > this.height &&
params.quality === undef &&
params.preserve_headers &&
!params.crop
) {
this.destroy();
return cb(blob);
}
// otherwise downsize
img.downsize(params.width, params.height, params.crop, params.preserve_headers);
};
img.onresize = function() {
cb(this.getAsBlob(blob.type, params.quality));
this.destroy();
};
img.onerror = function() {
cb(blob);
};
img.load(blob);
} catch(ex) {
cb(blob);
}
}
function setOption(option, value, init) {
var self = this, reinitRequired = false;
function _setOption(option, value, init) {
var oldValue = settings[option];
switch (option) {
case 'max_file_size':
if (option === 'max_file_size') {
settings.max_file_size = settings.filters.max_file_size = value;
}
break;
case 'chunk_size':
if (value = plupload.parseSize(value)) {
settings[option] = value;
settings.send_file_name = true;
}
break;
case 'multipart':
settings[option] = value;
if (!value) {
settings.send_file_name = true;
}
break;
case 'unique_names':
settings[option] = value;
if (value) {
settings.send_file_name = true;
}
break;
case 'filters':
// for sake of backward compatibility
if (plupload.typeOf(value) === 'array') {
value = {
mime_types: value
};
}
if (init) {
plupload.extend(settings.filters, value);
} else {
settings.filters = value;
}
// if file format filters are being updated, regenerate the matching expressions
if (value.mime_types) {
settings.filters.mime_types.regexp = (function(filters) {
var extensionsRegExp = [];
plupload.each(filters, function(filter) {
plupload.each(filter.extensions.split(/,/), function(ext) {
if (/^\s*\*\s*$/.test(ext)) {
extensionsRegExp.push('\\.*');
} else {
extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&'));
}
});
});
return new RegExp('(' + extensionsRegExp.join('|') + ')$', 'i');
}(settings.filters.mime_types));
}
break;
case 'resize':
if (init) {
plupload.extend(settings.resize, value, {
enabled: true
});
} else {
settings.resize = value;
}
break;
case 'prevent_duplicates':
settings.prevent_duplicates = settings.filters.prevent_duplicates = !!value;
break;
case 'browse_button':
case 'drop_element':
value = plupload.get(value);
case 'container':
case 'runtimes':
case 'multi_selection':
case 'flash_swf_url':
case 'silverlight_xap_url':
settings[option] = value;
if (!init) {
reinitRequired = true;
}
break;
default:
settings[option] = value;
}
if (!init) {
self.trigger('OptionChanged', option, value, oldValue);
}
}
if (typeof(option) === 'object') {
plupload.each(option, function(value, option) {
_setOption(option, value, init);
});
} else {
_setOption(option, value, init);
}
if (init) {
// Normalize the list of required capabilities
settings.required_features = normalizeCaps(plupload.extend({}, settings));
// Come up with the list of capabilities that can affect default mode in a multi-mode runtimes
preferred_caps = normalizeCaps(plupload.extend({}, settings, {
required_features: true
}));
} else if (reinitRequired) {
self.trigger('Destroy');
initControls.call(self, settings, function(inited) {
if (inited) {
self.runtime = o.Runtime.getInfo(getRUID()).type;
self.trigger('Init', { runtime: self.runtime });
self.trigger('PostInit');
} else {
self.trigger('Error', {
code : plupload.INIT_ERROR,
message : plupload.translate('Init error.')
});
}
});
}
}
// Internal event handlers
function onBeforeUpload(up, file) {
// Generate unique target filenames
if (up.settings.unique_names) {
var matches = file.name.match(/\.([^.]+)$/), ext = "part";
if (matches) {
ext = matches[1];
}
file.target_name = file.id + '.' + ext;
}
}
function onUploadFile(up, file) {
var url = up.settings.url
, chunkSize = up.settings.chunk_size
, retries = up.settings.max_retries
, features = up.features
, offset = 0
, blob
;
// make sure we start at a predictable offset
if (file.loaded) {
offset = file.loaded = chunkSize ? chunkSize * Math.floor(file.loaded / chunkSize) : 0;
}
function handleError() {
if (retries-- > 0) {
delay(uploadNextChunk, 1000);
} else {
file.loaded = offset; // reset all progress
up.trigger('Error', {
code : plupload.HTTP_ERROR,
message : plupload.translate('HTTP Error.'),
file : file,
response : xhr.responseText,
status : xhr.status,
responseHeaders: xhr.getAllResponseHeaders()
});
}
}
function uploadNextChunk() {
var chunkBlob, formData, args = {}, curChunkSize;
// make sure that file wasn't cancelled and upload is not stopped in general
if (file.status !== plupload.UPLOADING || up.state === plupload.STOPPED) {
return;
}
// send additional 'name' parameter only if required
if (up.settings.send_file_name) {
args.name = file.target_name || file.name;
}
if (chunkSize && features.chunks && blob.size > chunkSize) { // blob will be of type string if it was loaded in memory
curChunkSize = Math.min(chunkSize, blob.size - offset);
chunkBlob = blob.slice(offset, offset + curChunkSize);
} else {
curChunkSize = blob.size;
chunkBlob = blob;
}
// If chunking is enabled add corresponding args, no matter if file is bigger than chunk or smaller
if (chunkSize && features.chunks) {
// Setup query string arguments
if (up.settings.send_chunk_number) {
args.chunk = Math.ceil(offset / chunkSize);
args.chunks = Math.ceil(blob.size / chunkSize);
} else { // keep support for experimental chunk format, just in case
args.offset = offset;
args.total = blob.size;
}
}
xhr = new o.XMLHttpRequest();
// Do we have upload progress support
if (xhr.upload) {
xhr.upload.onprogress = function(e) {
file.loaded = Math.min(file.size, offset + e.loaded);
up.trigger('UploadProgress', file);
};
}
xhr.onload = function() {
// check if upload made itself through
if (xhr.status >= 400) {
handleError();
return;
}
retries = up.settings.max_retries; // reset the counter
// Handle chunk response
if (curChunkSize < blob.size) {
chunkBlob.destroy();
offset += curChunkSize;
file.loaded = Math.min(offset, blob.size);
up.trigger('ChunkUploaded', file, {
offset : file.loaded,
total : blob.size,
response : xhr.responseText,
status : xhr.status,
responseHeaders: xhr.getAllResponseHeaders()
});
// stock Android browser doesn't fire upload progress events, but in chunking mode we can fake them
if (o.Env.browser === 'Android Browser') {
// doesn't harm in general, but is not required anywhere else
up.trigger('UploadProgress', file);
}
} else {
file.loaded = file.size;
}
chunkBlob = formData = null; // Free memory
// Check if file is uploaded
if (!offset || offset >= blob.size) {
// If file was modified, destory the copy
if (file.size != file.origSize) {
blob.destroy();
blob = null;
}
up.trigger('UploadProgress', file);
file.status = plupload.DONE;
up.trigger('FileUploaded', file, {
response : xhr.responseText,
status : xhr.status,
responseHeaders: xhr.getAllResponseHeaders()
});
} else {
// Still chunks left
delay(uploadNextChunk, 1); // run detached, otherwise event handlers interfere
}
};
xhr.onerror = function() {
handleError();
};
xhr.onloadend = function() {
this.destroy();
xhr = null;
};
// Build multipart request
if (up.settings.multipart && features.multipart) {
xhr.open("post", url, true);
// Set custom headers
plupload.each(up.settings.headers, function(value, name) {
xhr.setRequestHeader(name, value);
});
formData = new o.FormData();
// Add multipart params
plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) {
formData.append(name, value);
});
// Add file and send it
formData.append(up.settings.file_data_name, chunkBlob);
xhr.send(formData, {
runtime_order: up.settings.runtimes,
required_caps: up.settings.required_features,
preferred_caps: preferred_caps,
swf_url: up.settings.flash_swf_url,
xap_url: up.settings.silverlight_xap_url
});
} else {
// if no multipart, send as binary stream
url = plupload.buildUrl(up.settings.url, plupload.extend(args, up.settings.multipart_params));
xhr.open("post", url, true);
xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // Binary stream header
// Set custom headers
plupload.each(up.settings.headers, function(value, name) {
xhr.setRequestHeader(name, value);
});
xhr.send(chunkBlob, {
runtime_order: up.settings.runtimes,
required_caps: up.settings.required_features,
preferred_caps: preferred_caps,
swf_url: up.settings.flash_swf_url,
xap_url: up.settings.silverlight_xap_url
});
}
}
blob = file.getSource();
// Start uploading chunks
if (up.settings.resize.enabled && runtimeCan(blob, 'send_binary_string') && !!~o.inArray(blob.type, ['image/jpeg', 'image/png'])) {
// Resize if required
resizeImage.call(this, blob, up.settings.resize, function(resizedBlob) {
blob = resizedBlob;
file.size = resizedBlob.size;
uploadNextChunk();
});
} else {
uploadNextChunk();
}
}
function onUploadProgress(up, file) {
calcFile(file);
}
function onStateChanged(up) {
if (up.state == plupload.STARTED) {
// Get start time to calculate bps
startTime = (+new Date());
} else if (up.state == plupload.STOPPED) {
// Reset currently uploading files
for (var i = up.files.length - 1; i >= 0; i--) {
if (up.files[i].status == plupload.UPLOADING) {
up.files[i].status = plupload.QUEUED;
calc();
}
}
}
}
function onCancelUpload() {
if (xhr) {
xhr.abort();
}
}
function onFileUploaded(up) {
calc();
// Upload next file but detach it from the error event
// since other custom listeners might want to stop the queue
delay(function() {
uploadNext.call(up);
}, 1);
}
function onError(up, err) {
if (err.code === plupload.INIT_ERROR) {
up.destroy();
}
// Set failed status if an error occured on a file
else if (err.file) {
err.file.status = plupload.FAILED;
calcFile(err.file);
// Upload next file but detach it from the error event
// since other custom listeners might want to stop the queue
if (up.state == plupload.STARTED) { // upload in progress
up.trigger('CancelUpload');
delay(function() {
uploadNext.call(up);
}, 1);
}
}
}
function onDestroy(up) {
up.stop();
// Purge the queue
plupload.each(files, function(file) {
file.destroy();
});
files = [];
if (fileInputs.length) {
plupload.each(fileInputs, function(fileInput) {
fileInput.destroy();
});
fileInputs = [];
}
if (fileDrops.length) {
plupload.each(fileDrops, function(fileDrop) {
fileDrop.destroy();
});
fileDrops = [];
}
preferred_caps = {};
disabled = false;
startTime = xhr = null;
total.reset();
}
// Default settings
settings = {
runtimes: o.Runtime.order,
max_retries: 0,
chunk_size: 0,
multipart: true,
multi_selection: true,
file_data_name: 'file',
flash_swf_url: 'js/Moxie.swf',
silverlight_xap_url: 'js/Moxie.xap',
filters: {
mime_types: [],
prevent_duplicates: false,
max_file_size: 0
},
resize: {
enabled: false,
preserve_headers: true,
crop: false
},
send_file_name: true,
send_chunk_number: true
};
setOption.call(this, options, null, true);
// Inital total state
total = new plupload.QueueProgress();
// Add public methods
plupload.extend(this, {
/**
* Unique id for the Uploader instance.
*
* @property id
* @type String
*/
id : uid,
uid : uid, // mOxie uses this to differentiate between event targets
/**
* Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED.
* These states are controlled by the stop/start methods. The default value is STOPPED.
*
* @property state
* @type Number
*/
state : plupload.STOPPED,
/**
* Map of features that are available for the uploader runtime. Features will be filled
* before the init event is called, these features can then be used to alter the UI for the end user.
* Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize.
*
* @property features
* @type Object
*/
features : {},
/**
* Current runtime name.
*
* @property runtime
* @type String
*/
runtime : null,
/**
* Current upload queue, an array of File instances.
*
* @property files
* @type Array
* @see plupload.File
*/
files : files,
/**
* Object with name/value settings.
*
* @property settings
* @type Object
*/
settings : settings,
/**
* Total progess information. How many files has been uploaded, total percent etc.
*
* @property total
* @type plupload.QueueProgress
*/
total : total,
/**
* Initializes the Uploader instance and adds internal event listeners.
*
* @method init
*/
init : function() {
var self = this;
if (typeof(settings.preinit) == "function") {
settings.preinit(self);
} else {
plupload.each(settings.preinit, function(func, name) {
self.bind(name, func);
});
}
bindEventListeners.call(this);
// Check for required options
if (!settings.browse_button || !settings.url) {
this.trigger('Error', {
code : plupload.INIT_ERROR,
message : plupload.translate('Init error.')
});
return;
}
initControls.call(this, settings, function(inited) {
if (typeof(settings.init) == "function") {
settings.init(self);
} else {
plupload.each(settings.init, function(func, name) {
self.bind(name, func);
});
}
if (inited) {
self.runtime = o.Runtime.getInfo(getRUID()).type;
self.trigger('Init', { runtime: self.runtime });
self.trigger('PostInit');
} else {
self.trigger('Error', {
code : plupload.INIT_ERROR,
message : plupload.translate('Init error.')
});
}
});
},
/**
* Set the value for the specified option(s).
*
* @method setOption
* @since 2.1
* @param {String|Object} option Name of the option to change or the set of key/value pairs
* @param {Mixed} [value] Value for the option (is ignored, if first argument is object)
*/
setOption: function(option, value) {
setOption.call(this, option, value, !this.runtime); // until runtime not set we do not need to reinitialize
},
/**
* Get the value for the specified option or the whole configuration, if not specified.
*
* @method getOption
* @since 2.1
* @param {String} [option] Name of the option to get
* @return {Mixed} Value for the option or the whole set
*/
getOption: function(option) {
if (!option) {
return settings;
}
return settings[option];
},
/**
* Refreshes the upload instance by dispatching out a refresh event to all runtimes.
* This would for example reposition flash/silverlight shims on the page.
*
* @method refresh
*/
refresh : function() {
if (fileInputs.length) {
plupload.each(fileInputs, function(fileInput) {
fileInput.trigger('Refresh');
});
}
this.trigger('Refresh');
},
/**
* Starts uploading the queued files.
*
* @method start
*/
start : function() {
if (this.state != plupload.STARTED) {
this.state = plupload.STARTED;
this.trigger('StateChanged');
uploadNext.call(this);
}
},
/**
* Stops the upload of the queued files.
*
* @method stop
*/
stop : function() {
if (this.state != plupload.STOPPED) {
this.state = plupload.STOPPED;
this.trigger('StateChanged');
this.trigger('CancelUpload');
}
},
/**
* Disables/enables browse button on request.
*
* @method disableBrowse
* @param {Boolean} disable Whether to disable or enable (default: true)
*/
disableBrowse : function() {
disabled = arguments[0] !== undef ? arguments[0] : true;
if (fileInputs.length) {
plupload.each(fileInputs, function(fileInput) {
fileInput.disable(disabled);
});
}
this.trigger('DisableBrowse', disabled);
},
/**
* Returns the specified file object by id.
*
* @method getFile
* @param {String} id File id to look for.
* @return {plupload.File} File object or undefined if it wasn't found;
*/
getFile : function(id) {
var i;
for (i = files.length - 1; i >= 0; i--) {
if (files[i].id === id) {
return files[i];
}
}
},
/**
* Adds file to the queue programmatically. Can be native file, instance of Plupload.File,
* instance of mOxie.File, input[type="file"] element, or array of these. Fires FilesAdded,
* if any files were added to the queue. Otherwise nothing happens.
*
* @method addFile
* @since 2.0
* @param {plupload.File|mOxie.File|File|Node|Array} file File or files to add to the queue.
* @param {String} [fileName] If specified, will be used as a name for the file
*/
addFile : function(file, fileName) {
var self = this
, queue = []
, ruid
;
function filterFile(file, cb) {
var queue = [];
o.each(self.settings.filters, function(rule, name) {
if (fileFilters[name]) {
queue.push(function(cb) {
fileFilters[name].call(self, rule, file, function(res) {
cb(!res);
});
});
}
});
o.inSeries(queue, cb);
}
/**
* @method resolveFile
* @private
* @param {o.File|o.Blob|plupload.File|File|Blob|input[type="file"]} file
*/
function resolveFile(file) {
var type = o.typeOf(file);
// o.File
if (file instanceof o.File) {
if (!file.ruid && !file.isDetached()) {
if (!ruid) { // weird case
return false;
}
file.ruid = ruid;
file.connectRuntime(ruid);
}
resolveFile(new plupload.File(file));
}
// o.Blob
else if (file instanceof o.Blob) {
resolveFile(file.getSource());
file.destroy();
}
// plupload.File - final step for other branches
else if (file instanceof plupload.File) {
if (fileName) {
file.name = fileName;
}
queue.push(function(cb) {
// run through the internal and user-defined filters, if any
filterFile(file, function(err) {
if (!err) {
files.push(file);
self.trigger("FileFiltered", file);
}
delay(cb, 1); // do not build up recursions or eventually we might hit the limits
});
});
}
// native File or blob
else if (o.inArray(type, ['file', 'blob']) !== -1) {
resolveFile(new o.File(null, file));
}
// input[type="file"]
else if (type === 'node' && o.typeOf(file.files) === 'filelist') {
// if we are dealing with input[type="file"]
o.each(file.files, resolveFile);
}
// mixed array of any supported types (see above)
else if (type === 'array') {
fileName = null; // should never happen, but unset anyway to avoid funny situations
o.each(file, resolveFile);
}
}
ruid = getRUID();
resolveFile(file);
if (queue.length) {
o.inSeries(queue, function() {
// if any files left after filtration, trigger FilesAdded
if (files.length) {
self.trigger("FilesAdded", files);
}
});
}
},
/**
* Removes a specific file.
*
* @method removeFile
* @param {plupload.File|String} file File to remove from queue.
*/
removeFile : function(file) {
var id = typeof(file) === 'string' ? file : file.id;
for (var i = files.length - 1; i >= 0; i--) {
if (files[i].id === id) {
return this.splice(i, 1)[0];
}
}
},
/**
* Removes part of the queue and returns the files removed. This will also trigger the FilesRemoved and QueueChanged events.
*
* @method splice
* @param {Number} start (Optional) Start index to remove from.
* @param {Number} length (Optional) Lengh of items to remove.
* @return {Array} Array of files that was removed.
*/
splice : function(start, length) {
// Splice and trigger events
var removed = files.splice(start === undef ? 0 : start, length === undef ? files.length : length);
// if upload is in progress we need to stop it and restart after files are removed
var restartRequired = false;
if (this.state == plupload.STARTED) { // upload in progress
plupload.each(removed, function(file) {
if (file.status === plupload.UPLOADING) {
restartRequired = true; // do not restart, unless file that is being removed is uploading
return false;
}
});
if (restartRequired) {
this.stop();
}
}
this.trigger("FilesRemoved", removed);
// Dispose any resources allocated by those files
plupload.each(removed, function(file) {
file.destroy();
});
if (restartRequired) {
this.start();
}
return removed;
},
/**
* Dispatches the specified event name and it's arguments to all listeners.
*
*
* @method trigger
* @param {String} name Event name to fire.
* @param {Object..} Multiple arguments to pass along to the listener functions.
*/
/**
* Check whether uploader has any listeners to the specified event.
*
* @method hasEventListener
* @param {String} name Event name to check for.
*/
/**
* Adds an event listener by name.
*
* @method bind
* @param {String} name Event name to listen for.
* @param {function} func Function to call ones the event gets fired.
* @param {Object} scope Optional scope to execute the specified function in.
*/
bind : function(name, func, scope) {
var self = this;
// adapt moxie EventTarget style to Plupload-like
plupload.Uploader.prototype.bind.call(this, name, function() {
var args = [].slice.call(arguments);
args.splice(0, 1, self); // replace event object with uploader instance
return func.apply(this, args);
}, 0, scope);
},
/**
* Removes the specified event listener.
*
* @method unbind
* @param {String} name Name of event to remove.
* @param {function} func Function to remove from listener.
*/
/**
* Removes all event listeners.
*
* @method unbindAll
*/
/**
* Destroys Plupload instance and cleans after itself.
*
* @method destroy
*/
destroy : function() {
this.trigger('Destroy');
settings = total = null; // purge these exclusively
this.unbindAll();
}
});
};
plupload.Uploader.prototype = o.EventTarget.instance;
/**
* Constructs a new file instance.
*
* @class File
* @constructor
*
* @param {Object} file Object containing file properties
* @param {String} file.name Name of the file.
* @param {Number} file.size File size.
*/
plupload.File = (function() {
var filepool = {};
function PluploadFile(file) {
plupload.extend(this, {
/**
* File id this is a globally unique id for the specific file.
*
* @property id
* @type String
*/
id: plupload.guid(),
/**
* File name for example "myfile.gif".
*
* @property name
* @type String
*/
name: file.name || file.fileName,
/**
* File type, `e.g image/jpeg`
*
* @property type
* @type String
*/
type: file.type || '',
/**
* File size in bytes (may change after client-side manupilation).
*
* @property size
* @type Number
*/
size: file.size || file.fileSize,
/**
* Original file size in bytes.
*
* @property origSize
* @type Number
*/
origSize: file.size || file.fileSize,
/**
* Number of bytes uploaded of the files total size.
*
* @property loaded
* @type Number
*/
loaded: 0,
/**
* Number of percentage uploaded of the file.
*
* @property percent
* @type Number
*/
percent: 0,
/**
* Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE.
*
* @property status
* @type Number
* @see plupload
*/
status: plupload.QUEUED,
/**
* Date of last modification.
*
* @property lastModifiedDate
* @type {String}
*/
lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString(), // Thu Aug 23 2012 19:40:00 GMT+0400 (GET)
/**
* Returns native window.File object, when it's available.
*
* @method getNative
* @return {window.File} or null, if plupload.File is of different origin
*/
getNative: function() {
var file = this.getSource().getSource();
return o.inArray(o.typeOf(file), ['blob', 'file']) !== -1 ? file : null;
},
/**
* Returns mOxie.File - unified wrapper object that can be used across runtimes.
*
* @method getSource
* @return {mOxie.File} or null
*/
getSource: function() {
if (!filepool[this.id]) {
return null;
}
return filepool[this.id];
},
/**
* Destroys plupload.File object.
*
* @method destroy
*/
destroy: function() {
var src = this.getSource();
if (src) {
src.destroy();
delete filepool[this.id];
}
}
});
filepool[this.id] = file;
}
return PluploadFile;
}());
/**
* Constructs a queue progress.
*
* @class QueueProgress
* @constructor
*/
plupload.QueueProgress = function() {
var self = this; // Setup alias for self to reduce code size when it's compressed
/**
* Total queue file size.
*
* @property size
* @type Number
*/
self.size = 0;
/**
* Total bytes uploaded.
*
* @property loaded
* @type Number
*/
self.loaded = 0;
/**
* Number of files uploaded.
*
* @property uploaded
* @type Number
*/
self.uploaded = 0;
/**
* Number of files failed to upload.
*
* @property failed
* @type Number
*/
self.failed = 0;
/**
* Number of files yet to be uploaded.
*
* @property queued
* @type Number
*/
self.queued = 0;
/**
* Total percent of the uploaded bytes.
*
* @property percent
* @type Number
*/
self.percent = 0;
/**
* Bytes uploaded per second.
*
* @property bytesPerSec
* @type Number
*/
self.bytesPerSec = 0;
/**
* Resets the progress to it's initial values.
*
* @method reset
*/
self.reset = function() {
self.size = self.loaded = self.uploaded = self.failed = self.queued = self.percent = self.bytesPerSec = 0;
};
};
window.plupload = plupload;
}(window, mOxie));
// source --> https://wp.glencommunity.org/wp-content/plugins/js_composer/assets/js/vendors/woocommerce-add-to-cart.js?ver=5.1
window.jQuery( document ).ready( function ( $ ) {
$( 'body' ).on( 'adding_to_cart', function ( event, $button, data ) {
$button && $button.hasClass( 'vc_gitem-link' ) && $button
.addClass( 'vc-gitem-add-to-cart-loading-btn' )
.parents( '.vc_grid-item-mini' )
.addClass( 'vc-woocommerce-add-to-cart-loading' )
.append( $( '